前言
前面我们介绍了简单工厂模式,它将创建对象的细节隐藏起来,客户端仅仅通过参数来决定生产哪种具体的产品。但是该模式有个较大的缺点,当我们增加新产品的时候就需要修改工厂类的逻辑,导致原来的商品创建逻辑可能受到影响,即违背了开闭原则。因此我们提出了工厂方法模式。
一、场景问题
在实际工作中,我们经常需要导出一些数据,这些数据可能是Excel模式、文本格式、XML格式或数据库脚本格式等等,总之一系列的数据文件格式,未来也有各种各样的导出格式需求.
二、解决方案
1、传统解决方案
即简单工厂模式,缺点上面也说明了,违背开闭原则。我们来重点讲述一下工厂方法模式
2、工厂方法模式方案
考虑到新增数据格式的扩展性,我们可以考虑将每种数据格式的导出抽取成单独的类,原来的工厂类可以抽象化成抽象类或者接口,只定义导出数据的方法,而交给子类去实现。这样新增数据格式时只需要新建个对应的工厂类继承原工厂抽象类或接口即可。
2.1、代码实现
-
首先我们定义出抽象数据格式接口和抽象工厂接口(这里我们使用接口)
//数据导出工具 public interface ExportDataApi { /** * 导出数据 * * @param data 数据 * @return void * @author xianzilei **/ void exportData(String data); } public interface ExportDataApiFactory { /** * 创建数据导出工具 * * @return 数据导出工具 * @author xianzilei **/ ExportDataApi createExportDataApi(String date); }
-
分别定义数据导出工具和工厂接口的实现
-
数据导出工具实现
//Excel格式 public class ExcelExportDataApi implements ExportDataApi { @Override public void exportData(String data) { System.out.println("将数据[" + data + "]导出为Excel格式..."); } } //XML格式 public class XmlExportDataApi implements ExportDataApi { @Override public void exportData(String data) { System.out.println("将数据[" + data + "]导出为XML格式..."); } } //文本格式 public class TextExportDataApi implements ExportDataApi { @Override public void exportData(String data) { System.out.println("将数据[" + data + "]导出为文本格式..."); } }
-
工厂接口实现
//Excel格式工厂 public class ExcelExportDataApiFactory implements ExportDataApiFactory { @Override public ExportDataApi createExportDataApi() { //返回Excel导出工具 return new ExcelExportDataApi(); } } //XML格式工厂 public class XmlExportDataApiFactory implements ExportDataApiFactory { @Override public ExportDataApi createExportDataApi() { //返回XML导出工具 return new XmlExportDataApi(); } } //文本格式工厂 public class TextExportDataApiFactory implements ExportDataApiFactory { @Override public ExportDataApi createExportDataApi() { //返回文本导出工具 return new TextExportDataApi(); } }
-
-
客户端代码
public static void main(String[] args) { //XML格式导出 ExportDataApiFactory factory = new XmlExportDataApiFactory(); ExportDataApi exportDataApi = factory.createExportDataApi(); exportDataApi.exportData("xianzilei 真帅!"); //文本格式导出 ExportDataApiFactory factory2 = new TextExportDataApiFactory(); ExportDataApi exportDataApi2 = factory2.createExportDataApi(); exportDataApi2.exportData("xianzilei 真帅!"); }
输出:
将数据[xianzilei 真帅!]导出为XML格式... 将数据[xianzilei 真帅!]导出为文本格式...
2.2、方案解析
根据上面的代码我们可以看到,我们定义出首先导出工具和工具工厂接口,当新增一种数据格式时,我们无需修改工厂类,只需要新增一个导出工具实现类和工具工厂实现类,完全不影响原有的数据格式导出逻辑,完美遵循了开闭原则。
三、模式剖析
从简单工厂模式到工厂方法模式的转换,大家是不是对工厂方法有个一定的认识,接下来请听我细细剖析。
1、模式定义
工厂方法模式(Simple Factory Pattern):又称工厂模式、多态工厂模式和虚拟构造器模式。定义一个创建产品对象的工厂接口,将产品对象的实际创建工作延迟到具体子工厂类当中。
工厂方法模式是针对简单工厂模式的改进,同时也符合创建型设计模式的特点——将创建与使用分离。模式本质:延迟到子类选择实现。
2、模式结构
工厂方法模式一般包含如下角色
-
Creator
:抽象工厂角色抽象工厂的接口,定义工厂的创建方法,但是交给子类实现,一般定义为抽象类或接口。
-
ConcreteCreator
:具体工厂角色抽象工厂的具体实现类,负责实现具体产品的创建细节。
-
Product
:抽象产品角色抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口(也可以是抽象类)
-
ConcreteProduct
:具体产品角色抽象产品角色的实现类
3、模式结构图
4、优缺点
- 优点
- 遵循开闭原则,可扩展性好。
- 遵循单一职责原则,每个工厂类只负责单一产品的创建
- 可以形成基于继承的等级结构
- 可以基于反射、配置化的方式完全屏蔽工厂的具体实现,即对客户端屏蔽具体工厂(详细见下文)
- 缺点
- 当扩展一个新产品时,除了要定义出具体的产品实现,还要定义与之对应的工厂实现,从而导致系统类的个数增加,一定程度上增加了系统的复杂度。
5、使用场景
- 当客户端不关心自己需要的产品类的具体实现时,可以考虑使用(这一点与简单工厂模式类似)
- 对产品的扩展性要求高的场景
四、模式扩展
1、基于反射和配置
上面的案例中客户端需要明确创建出具体工厂,如果客户想要修改产品的获取,还需要修改客户端代码,因此可以采用配置文件的方式,这样只需要配置文件内容,无需修改代码即可实现产品的替换。
-
首先定义配置文件(这里使用xml格式)
<?xml version="1.0"?> <config> <className>com.jicl.design.factorypatten.factorymethod.XmlExportDataApiFactory</className> </config>
-
创建一个XML解析类
public class XmlUtil { /** * 解析配置文件获取bean * * @param configPath 配置文件路径 * @return 目标对象 **/ public static Object getBean(String configPath) throws Exception { //创建DOM文档对象 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); //读取配置文件 Document document = documentBuilder.parse(new File(configPath)); //获取包含类名的文本节点 NodeList nodeList = document.getElementsByTagName("className"); //获取节点值 Node node = nodeList.item(0).getFirstChild(); String className = node.getNodeValue(); //反射创建类 Class<?> c = Class.forName(className); return c.newInstance(); } }
-
客户端代码可以修改为如下
public static void main(String[] args) throws Exception { //读取配置文件获取指定的工厂类 ExportDataApiFactory factory = (ExportDataApiFactory)XmlUtil.getBean("config.xml"); //创建数据导出工具类 ExportDataApi exportDataApi = factory.createExportDataApi(); //数据导出 exportDataApi.exportData("xianzilei 真帅!"); }
输出
将数据[xianzilei 真帅!]导出为XML格式...
如果想要其他格式的输出,只需要修改配置文件中的工厂实现类全路径名即可
2、工厂方法的隐藏
上面的案例首先是工厂类创建数据导出工具,然后调用数据导出工具来进行实际的数据导出工作。有时候为了进一步隐藏数据导出细节,可以仅仅给客户端提供数据导出的入口,内部工厂类创建数据导出工具,然后调用数据导出工具操作完全对客户端隐藏。
这样我们可以考虑将抽象工厂接口修改为抽象类,在抽象类中提供出个方法出来供客户端调用,细节全部隐藏在这个方法中,即对客户端透明。实现代码如下
-
修改抽象工厂接口为抽象类
public abstract class ExportDataApiFactory { /** * 数据导出:供客户端调用 * * @param data 数据 * @return void * @author xianzilei **/ public void exportData(String data) { //创建工具 ExportDataApi exportDataApi = createExportDataApi(); //导出数据 exportDataApi.exportData(data); } /** * 创建数据导出工具 * * @return 数据导出工具 * @author xianzilei * @date 2020/9/18 16:14 **/ protected abstract ExportDataApi createExportDataApi(); }
-
修改客户端代码
public static void main(String[] args) throws Exception { //读取配置文件获取指定的工厂类 ExportDataApiFactory factory = (ExportDataApiFactory)XmlUtil.getBean("config.xml"); //直接使用工厂类的数据导出 factory.exportData("xianzilei 真帅!"); }
输出
将数据[xianzilei 真帅!]导出为XML格式...