《研磨设计模式》之工厂方法模式

一、应用场景

实现一个能导出数据到各种文件格式(txt,DBFile,Excel,Xml)的应用框架.

1.不管用户导出神马样的文件格式,都要进行一个导出的操作.

2.系统不知道用户会导出哪种格式的文件

书中描述,框架是一个半成品,开发者可以在此基础上完成软件功能。因此具体是导出神马样的格式,是由具体的开发者(本例是Client类)决定的.

根据1.都要进行一个导出操作,因此要定义具有导出功能的接口

1: /**
2:  * 定义数据导出的功能的接口
3:  * @author YoungCold
4:  *
5:  */
6: public interface ExportFileApi {
7:     /**
8:      * 将数据导出成文件(具体文件格式有实现类决定)
9:      * @param data
10:      * @return
11:      */
12:     public boolean export(String data);
13: }
对于导出数据的业务功能对象,也就是用ExportFileApi定义的导出数据的方法export()完成导出数据这一功能的类的实例.它需要根据用户的需要(比如,用户选择导出数据到Excel文件)创建相应的ExportFileApi对象(实现ExportFileApi定义的export():将数据导出到Excel),也就是说特定的ExportFileApi实现由实际情况决定。
问题是,业务功能对象不知道如何创建ExportFileApi的对象(实际上,也不需要知道)
二、工厂模式
工厂方法的定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类①,Factory Method使一个类②的实例化延迟到子类。
补充:就对应用场景而言,个人感觉,①和②指的是同一个类——实现ExportFileApi的类。
 
前面提过,实现导出功能的业务类跟本不知道实际会选择哪种导出数据的格式,因此该对象就不应该和具体的导出文件对象(实现ExportFileApi的类)耦合在一起,只需要知道ExportFileApi接口即可。
 
这样一来,业务类要求不能和ExportFileApi具体实现类耦合,但业务类同时要实现导出数据的功能,这就必然要用到ExportFileApi具体实现类的功能。
解决方法:既然需要接口对象,那就定义一个方法创建接口对象。但事实上,业务类并不知道要创建哪种接口对象,因此创建接口对象的任务就由业务类的子类完成,比方说是导出txt文件的业务类(业务类的子类)完成,这样导出txt文件的业务类负责创建出接口对象,该接口对象完成数据导出到txt的功能。
所以,业务类的创建接口对象的方法就定义成抽象方法。
1.实现导出功能的业务类是个抽象类
2.具体的导出操作有业务类的子类完成
 
数据导出功能业务类
1: /**
2:  * 实现导出数据的业务功能对象
3:  * 即使用数据导出的工具类,实现实际的数据导出
4:  * @author YoungCold
5:  *
6:  */
7: public abstract class ExportOperate {
8:     public boolean export(String data){
9:         ExportFileApi exporter = createExportFileApi();
10:         return exporter.export(data);
11:     }
12:
13:     public abstract ExportFileApi createExportFileApi();
14: }
数据导出功能业务类的子类,实现具体的导出功能(创建何种接口对象)
 
接口对象(实现数据导出到txt文件)
1: /**
2:  * 导出数据的工具类,实现数据导出到文本文件
3:  * @author YoungCold
4:  *
5:  */
6: public class ExportTxtFile implements ExportFileApi {
7: 
8:     @Override
9:     public boolean export(String data) {
10:         System.out.println("导出数据"+data+"到文本文件");
11:         return true;
12:     }
13: 
14: }
 
接口对象(实现数据导出到数据库文件)
1: /**
2:  * 导出数据的工具类,实现数据导出到数据库文件
3:  * @author YoungCold
4:  *
5:  */
6: public class ExportDBFile implements ExportFileApi {
7: 
8:     @Override
9:     public boolean export(String data) {
10:         System.out.println("导出数据"+data+"到数据库文件");
11:         return true;
12:     }
13: 
14: }
 
数据导出业务类(负责创建 数据导出到txt文件的接口对象)
1: public class ExportTxtFileOperate extends ExportOperate {
2: 
3:     @Override
4:     public ExportFileApi createExportFileApi() {
5:         // 创建数据导出到txt文件的接口对象
6:         return new ExportTxtFile();
7:     }
8: 
9: }
 
数据导出业务类(负责创建 数据导出到数据库文件的接口对象)
1: public class ExportDBFileOperate extends ExportOperate {
2: 
3:     @Override
4:     public ExportFileApi createExportFileApi() {
5:         //  创建数据导出到数据库文件的接口对象
6:         return new ExportDBFile();
7:     }
8: 
9: }

 

这样,当用户选择数据导出到具体格式的文件时,我们只需要创建这种文件格式的业务类的对象即可。

比如,用户选择数据导出到数据库文件

1: String data = "2013-1-13";//要导出的数据
2: ExportOperate exporter1 = new ExportDBFileOperate();//
3: exporter1.export(data);//

用户选择数据导出到txt文件

1: String data = "2013-1-13";
2: ExportOperate exporter2 = new ExportTxtFileOperate();//
3: exporter2.export(data);
再来看数据导出业务抽象类,所实现的数据导出方法
1: public boolean export(String data){
2:
3:     return exporter.export(data);
4: }
ExportFileApi exporter = createExportFileApi();
业务类的导出功能代码中,没有和具体的接口对象耦合,根本不知道创建的哪一个接口对象,它只关心得到一个接口对象,再调用接口对象的导出方法,完成数据导出功能。
三、工厂方法的结构
 
Product:定义工厂方法所创建的对象的接口,也就是实际需要使用的对象接口
ExportFileApi
Creator:创建器,声明工厂方法,工厂方法通常会返回一个Product类的实例对象,而且多是抽象方法。也可以在Creator里面提供工厂方法的默认实现,让工厂方法返回一个缺省的Product类型的实例对象 ExportOperate
ConcreteProduct:具体Product接口的实现对象
ExportTxtFile ExportDBFile
ConcreteCreator:具体的创建对象,覆盖实现Creato定义的工厂方法,返回具体的Product实例
ExportTxtFileOperate ExportDBFileOperate