定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method 使一个类的实例化延迟到其子类
正如名字所示,工厂方法模式与简单工厂有几分相似。关于差异,后文将会介绍。
案例
例如有这样一个应用:实现一个导出数据的应用框架,来让客户选择导出方式,比如导出成文本格式,数据格式,xml格式等等。
依然,为了面向接口编程。我们首先定义一个接口,抽象了导出数据功能。
/**
* 各种导出形式的操作都遵循此接口
* Created by jerry on 16-4-21.
*/
public interface ExportFileApi {
/**
* 导出内容
* @param data 导出数据
* @return 是否成功
*/
public boolean export(String data);
}
现在如果我想要实现把数据导出文本格式或者导出成其他格式,那么实现此接口即可。
/**
* 将数据导出到txt文件中去
* Created by jerry on 16-4-21.
*/
public class ExportTxtFile implements ExportFileApi{
@Override
public boolean export(String data) {
System.out.println("导出数据: "+ data + " 到txt中");
return true;
}
}
/**
* 导出数据到数据库中
* Created by jerry on 16-4-21.
*/
public class ExportDB implements ExportFileApi{
@Override
public boolean export(String data) {
System.out.println("导出数据: " +data+ " 到数据库中");
return true;
}
}
问题
试想一个问题,当我们在设计时,我们还没有想好将来究竟是用哪个具体实现,例如究竟是导出到文件呢?还是数据库呢?还是其他?换句话说,关于具体采用哪种,我想之后再做决定。这时,为了让框架层代码能够不受影响,就需要工厂方法模式。
使用模式的情况
工厂方法模式的主要功能就是让父类在不知道具体实现的情况下,完成自身的功能调用,而具体实现延迟到子类来实现。通常情况下,父类是一个抽象类,里面包含了一个抽象方法,此方法即工厂方法。
/**
* 实现导出数据操作功能
* Created by jerry on 16-4-21.
*/
public abstract class ExportOperate {
/**
* 导出数据
* @param data
* @return
*/
public boolean export(String data) {
ExportFileApi api = factoryMethod();
return api.export(data);
}
/**
* 工厂方法,创建导出的文件对象的接口对象
* @return
*/
protected abstract ExportFileApi factoryMethod();
}
这样以后,继承ExportOperate
的类必须复写factoryMethod()
方法,即由子类来完成具体实现,在父类中无须关注到底是哪个具体实例负责具体的实现,父类只须面向接口编程,大大减少了耦合度。
/**
* 在这里真正创建具体对象
* Created by jerry on 16-4-21.
*/
public class ExportTxtFileOperate extends ExportOperate {
@Override
protected ExportFileApi factoryMethod() {
return new ExportTxtFile();
}
}
/**
* Created by jerry on 16-4-21.
*/
public class ExportDBOperate extends ExportOperate {
@Override
protected ExportFileApi factoryMethod() {
return new ExportDB();
}
}
客户端调用如下:
/**
* 测试类
* Created by jerry on 16-4-21.
*/
public class Client {
public static void main(String[] args) {
ExportOperate operate = new ExportDBOperate();
operate.export("test");
}
}
参数化工厂方法
工厂方法的设计中,可以使用参数,这样就可以通过参数决定到底使用哪一个具体的实现。
如果采用参数化工厂方法,那么通常工厂方法不再抽象,而是变为普通方法,并且提供默认的实现。此时,既然有了默认实现,工厂方法所在类也无须变为抽象,普通类即可。这样客户端可以直接使用这个类。
/**
* 实现导出数据操作功能
* Created by jerry on 16-4-21.
*/
public class ExportOperateByPara {
/**
* 导出数据
* @param data
* @param type 类型选择
* @return
*/
public boolean export(int type, String data) {
ExportFileApi api = factoryMethod(type);
return api.export(data);
}
/**
* 工厂方法,根据参数创建导出的文件对象的接口对象
* @param type
* @return
*/
protected ExportFileApi factoryMethod(int type) {
ExportFileApi api = null;
if (type == 1) {
api = new ExportTxtFile();
} else if (type == 2) {
api = new ExportDB();
}
return api;
}
}
由于有了默认实现,直接使用即可,客户端调用如下:
/**
* 测试类2
* Created by jerry on 16-4-21.
*/
public class Client2 {
public static void main(String[] args) {
ExportOperateByPara operate = new ExportOperateByPara();
operate.export(2,"test");
}
}
之所以使用参数化工厂方法,是因为其扩展起来会非常方便,已有的代码不会变,只要新加入一个子类来提供新的工厂方法实现,然后在客户端使用这个新的子类即可。
例如,我们要扩展一个导出xml文件的类:
/**
* 导出到xml
* Created by jerry on 16-4-21.
*/
public class ExportXml implements ExportFileApi {
@Override
public boolean export(String data) {
System.out.println("导出数据: " + data + " 到xml文件");
return true;
}
}
加入新的实现类,这里可以让子类选择性覆盖,进行功能增强,或者不想覆盖还可以返回去让父类来实现。
/**
* Created by jerry on 16-4-21.
*/
public class ExportXmlOperate extends ExportOperateByPara {
/**
* 覆写工厂方法
* @param type
* @return
*/
@Override
protected ExportFileApi factoryMethod(int type) {
ExportFileApi api = null;
// 这里是一个强大功能,可以让子类选择性覆盖,不想覆盖的方法可以返回去让父类实现
if (type == 3) {
api = new ExportXml();
} else {
api = super.factoryMethod(type);
}
return api;
}
}
/**
* 测试类2
* Created by jerry on 16-4-21.
*/
public class Client2 {
public static void main(String[] args) {
ExportOperateByPara operate = new ExportXmlOperate();
operate.export(1,"test1");
operate.export(2,"test2");
operate.export(3,"test3");
}
}
程序运行如下:
导出数据: test1 到txt中
导出数据: test2 到数据库中
导出数据: test3 到xml文件
这个易扩展性的获得,是因为工厂方法给子类提供了一个钩子,使得子类可以操作父类的方法,使得扩展新的对象版本变得非常容易。
与简单工厂模式的区别
如果把ExportOperateByPara
中的export
方法删掉,那么发现此类就是一个简单工厂类。所以两者其实非常相似,在具体实现上都是“选择实现”。但是不同点在于:简单工厂直接在工厂类里面进行“选择实现”,而工厂方法会把这个工作延迟到子类来实现。
总结
本质:延迟到子类来选择实现。
何时使用工厂方法模式:
- 如果一个类需要创建某个接口的对象,但是有不知道具体的实现。此时可选用工厂方法模式,把创建对象的工作延迟到子类中实现。
- 如果一个类本身就希望由它的子类来创建所需的对象的时候,应该使用工厂方法模式。
我的博客:http://shenchao.me/
Github: https://github.com/jerry-sc/designPattern
Reference:《研磨设计模式》