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

一、应用场景

实现一个能导出数据到各种文件格式(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