工厂方法模式-连续剧第一集

来源:互联网 发布:正规淘宝刷好评兼职 编辑:程序博客网 时间:2024/04/29 21:34

    做Java一晃就十年了,最近手痒痒,也决定跟随一下潮流,整个博客,写点东西,就算对自己的知识进行一个梳理和总结,也跟朋友们交流交流,希望能坚持下去。
    先写写设计模式方面的内容吧,就是GoF的23个模式,先从大家最熟悉的工厂方法模式开始,这个最简单,明白的人多,看看是否能写出点跟别人不一样的东西,欢迎大家来热烈讨论,提出建议或意见,并进行批评指正,一概虚心接受,在此先谢过了!
    另外,大家也可以说说最想看到哪个模式,那我就先写它,呵呵,大家感兴趣,我才会有动力写下去!好了,言归正传,Now Go!

工厂方法模式(Factory Method)

1  场景问题

1.1  导出数据的应用框架

        考虑这样一个实际应用:实现一个导出数据的应用框架,来让客户选择数据的导出方式,并真正执行数据导出。
        在一些实际的企业应用中,一个公司的系统往往分散在很多个不同的地方运行,比如各个分公司或者是门市点,公司没有建立全公司专网的实力,但是又不愿意让业务数据实时的在广域网上传递,一个是考虑数据安全的问题,一个是运行速度的问题。
        这种系统通常会有一个折中的方案,那就是各个分公司内运行系统的时候是独立的,是在自己分公司的局域网内运行。然后在每天业务结束的时候,各个分公司会导出自己的业务数据,然后把业务数据打包通过网络传送给总公司,或是专人把数据送到总公司,然后由总公司进行数据导入和核算。
        通常这种系统,在导出数据上,会有一些约定的方式,比如导出成:文本格式、数据库备份形式、Excel格式、Xml格式等等。
        现在就来考虑实现这样一个应用框架。在继续之前,先来了解一些关于框架的知识。

1.2  框架的基础知识

(1):框架是什么
        简单点说:框架就是能完成一定功能的半成品软件
        就其本质而言,框架是一个软件,而且是一个半成品的软件。所谓半成品,就是还不能完全实现用户需要的功能,框架只是实现用户需要的功能的一部分,还需要进一步加工,才能成为一个满足用户需要的、完整的软件。因此框架级的软件,它的主要客户是开发人员,而不是最终用户。
        有些朋友会想,既然框架只是个半成品,那何必要去学习和使用框架呢?学习成本也不算小,那就是因为框架能完成一定的功能,也就是这“框架已经完成的一定的功能”在吸引着开发人员,让大家投入去学习和使用框架。


(2):框架能干什么
        能完成一定功能,加快应用开发进度
        由于框架完成了一定的功能,而且通常是一些基础的、有难度的、通用的功能,这就避免我们在应用开发的时候完全从头开始,而是在框架已有的功能之上继续开发,也就是说会复用框架的功能,从而加快应用的开发进度。
        给我们一个精良的程序架构
        框架定义了应用的整体结构,包括类和对象的分割,各部分的主要责任,类和对象怎么协作,以及控制流程等等。
现在Java界大多数流行的框架,大都出自大师手笔,设计都很精良。基于这样的框架来开发,一般会遵循框架已经规划好的结构来进行开发,从而让我们开发的应用程序的结构也相对变得精良了。


(3):对框架的理解
        基于框架来开发,事情还是那些事情,只是看谁做的问题
        对于应用程序和框架的关系,可以用一个图来简单描述一下,如图1所示:


 图1  应用程序和框架的简单关系示意图

 

        如果没有框架,那么客户要求的所有功能都由开发人员自己来开发,没问题,同样可以实现用户要求的功能,只是开发人员的工作多点。
        如果有了框架,框架本身完成了一定的功能,那么框架已有的功能,开发人员就可以不做了,开发人员只需要完成框架没有的功能,最后同样是完成客户要求的所有功能,但是开发人员的工作就减少了。
        也就是说,基于框架来开发,软件要完成的功能并没有变化,还是客户要求的所有功能,也就是“事情还是那些事情”的意思。但是有了框架过后,框架完成了一部分功能,然后开发人员再完成一部分功能,最后由框架和开发人员合起来完成了整个软件的功能,也就是看这些功能“由谁做”的问题。

         基于框架开发,可以不去做框架所做的事情,但是应该明白框架在干什么,以及框架是如何实现相应功能的
        事实上,在实际开发中,应用程序和框架的关系,通常都不会如上面讲述的那样,分得那么清楚,更为普遍的是相互交互的,也就是应用程序做一部分工作,然后框架做一部分工作,然后应用程序再做一部分工作,然后框架再做一部分工作,如此交错,最后由应用程序和框架组合起来完成用户的功能要求。
        也用个图来说明,如图2所示:

 图2  应用程序和框架的关系示意图


        如果把这个由应用程序和框架组合在一起构成的矩形,当作最后完成的软件。试想一下,如果你不懂框架在干什么的话,相当于框架对你来讲是个黑盒,也就是相当于在上面图2中,去掉框架的两块,会发现什么?没错,剩下的应用程序是支离破碎的,是相互分隔开来的。
        这会导致一个非常致命的问题,整个应用是如何运转起来的,你是不清楚的,也就是说对你而言,项目已经失控了,从项目管理的角度来讲,这是很危险的。
        因此,在基于框架开发的时候,虽然我们可以不去做框架所做的事情,但是应该搞明白框架在干什么,如果条件许可的话,还应该搞清楚框架是如何实现相应功能的,至少应该把大致的实现思路和实现步骤搞清楚,这样我们才能整体的掌控整个项目,才能尽量减少出现项目失控的情况。


(4):框架和设计模式的关系
        设计模式比框架更抽象
       框架已经是实现出来的软件了,虽然只是个半成品的软件,但毕竟是已经实现出来的了。而设计模式的重心还在于解决问题的方案上,也就是还停留在思想的层面。因此设计模式比框架更为抽象。
      

       设计模式是比框架更小的体系结构元素
       如上所述,框架是已经实现出来的软件,并实现了一系列的功能,因此一个框架,通常会包含多个设计模式的应用。

        框架比设计模式更加特例化
       框架是完成一定功能的半成品软件,也就是说,框架的目的很明确,就是要解决某一个领域的某些问题,那是很具体的功能,不同的领域实现出来的框架是不一样的。
      而设计模式还停留在思想的层面,在不同的领域都可以应用,只要相应的问题适合用某个设计模式来解决。因此框架总是针对特定领域的,而设计模式更加注重从思想上,从方法上来解决问题,更加通用化。

1.3  有何问题
       分析上面要实现的应用框架,不管用户选择什么样的导出格式,最后导出的都是一个文件,而且系统并不知道究竟要导出成为什么样的文件,因此应该有一个统一的接口,来描述系统最后生成的对象,并操作输出的文件。
       先把导出的文件对象的接口定义出来,示例代码如下:

 

 

/**

 * 导出的文件对象的接口

 */

public interface ExportFileApi {

    /**

     * 导出内容成为文件

     * @param data 示意:需要保存的数据

     * @return 是否导出成功

     */

    public boolean export(String data);

}

        对于实现导出数据的业务功能对象,它应该根据需要来创建相应的ExportFileApi的实现对象,因为特定的ExportFileApi的实现是与具体的业务相关的。但是对于实现导出数据的业务功能对象而言,它并不知道应该创建哪一个ExportFileApi的实现对象,也不知道如何创建。
        也就是说:对于实现导出数据的业务功能对象,它需要创建ExportFileApi的具体实例对象,但是它只知道ExportFileApi接口,而不知道其具体的实现。那该怎么办呢?

 

 

 

 

 2  解决方案

2.1  工厂方法模式来解决

        用来解决上述问题的一个合理的解决方案就是工厂方法模式。那么什么是工厂方法模式呢?
(1)工厂方法模式定义
        定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到其子类。

 

(2)应用工厂方法模式来解决的思路
        仔细分析上面的问题,事实上在实现导出数据的业务功能对象里面,根本就不知道究竟要使用哪一种导出文件的格式,因此这个对象本就不应该和具体的导出文件的对象耦合在一起,它只需要面向导出的文件对象的接口就好了。
        但是这样一来,又有新的问题产生了:接口是不能直接使用的,需要使用具体的接口实现对象的实例。
        这不是自相矛盾吗?要求面向接口,不让和具体的实现耦合,但是又需要创建接口的具体实现对象的实例。怎么解决这个矛盾呢?
        工厂方法模式的解决思路很有意思,那就是不解决,采取无为而治的方式:不是需要接口对象吗,那就定义一个方法来创建;可是事实上它自己是不知道如何创建这个接口对象的,没有关系,那就定义成抽象方法就好了,自己实现不了,那就让子类来实现,这样这个对象本身就可以只是面向接口编程,而无需关心到底如何创建接口对象了。


2.2  模式结构和说明

        工厂方法模式的结构如图3所示:


                     图3  工厂方法模式结构示意图


Product
        定义工厂方法所创建的对象的接口,也就是实际需要使用的对象的接口。
ConcreteProduct:
        具体的Product接口的实现对象。
Creator:
        创建器,声明工厂方法,工厂方法通常会返回一个Product类型的实例对象,而且多是抽象方法。也可以在Creator里面提供工厂方法的默认实现,让工厂方法返回一个缺省的Product类型的实例对象。
ConcreteCreator:
        具体的创建器对象,覆盖实现Creator定义的工厂方法,返回具体的Product实例。


2.3  工厂方法模式示例代码

(1)先看看Product的定义,示例代码如下:

 

 

/**

 * 工厂方法所创建的对象的接口

 */

public interface Product {

    //可以定义Product的属性和方法

}


(2)再看看具体的Product的实现对象,示例代码如下:

 

 

/**

 * 具体的Product对象

 */

public class ConcreteProduct implements Product {

    //实现Product要求的方法

}

(3)接下来看看创建器的定义,示例代码如下:

 

 

/**

 * 创建器,声明工厂方法

 */

public abstract class Creator {

    /**

     * 创建Product的工厂方法

     * @return Product对象

     */

    protected abstract Product factoryMethod();

    /**

     * 示意方法,实现某些功能的方法

     */

    public void someOperation() {

       //通常在这些方法实现中,需要调用工厂方法来获取Product对象

       Product product = factoryMethod();

    }

}

(4)再看看具体的创建器实现对象,示例代码如下:

 

 

/**

 * 具体的创建器实现对象

 */

public class ConcreteCreator extends Creator {

    protected Product factoryMethod() {

       //重定义工厂方法,返回一个具体的Product对象

       return new ConcreteProduct();

    }

}

 

2.4  使用工厂方法模式来实现示例

        要使用工厂方法模式来实现示例,先来按照工厂方法模式的结构,对应出哪些是被创建的Product,哪些是Creator。分析要求实现的功能,导出的文件对象接口ExportFileApi就相当于是Product,而用来实现导出数据的业务功能对象就相当于Creator。把Product和Creator分开过后,就可以分别来实现它们了。
        使用工厂模式来实现示例的程序结构如图4所示:


                                 图4  使用工厂模式来实现示例的程序结构示意图
 下面一起来看看代码实现。
(1)导出的文件对象接口ExportFileApi的实现没有变化,这里就不去赘述了
(2)接下来看看接口ExportFileApi的实现,为了示例简单,只实现导出文本文件格式和数据库备份文件两种。先看看导出文本文件格式的实现,示例代码如下:

 

 

/**

 * 导出成文本文件格式的对象

 */

public class ExportTxtFile implements ExportFileApi{

    public boolean export(String data) {

       //简单示意一下,这里需要操作文件

       System.out.println("导出数据"+data+"到文本文件");

       return true;

    }

}

再看看导出成数据库备份文件形式的对象的实现,示例代码如下:

 

 

/**

 * 导出成数据库备份文件形式的对象

 */

public class ExportDB implements ExportFileApi{

    public boolean export(String data) {

       //简单示意一下,这里需要操作数据库和文件

       System.out.println("导出数据"+data+"到数据库备份文件");

       return true;

    }

}

(3)Creator这边的实现,首先看看ExportOperate的实现,示例代码如下:

 

 

/**

 * 实现导出数据的业务功能对象

 */

public abstract class ExportOperate {

    /**

     * 导出文件

     * @param data 需要保存的数据

     * @return 是否成功导出文件

     */

    public boolean export(String data){

       //使用工厂方法

       ExportFileApi api = factoryMethod();

       return api.export(data);

    }

    /**

     * 工厂方法,创建导出的文件对象的接口对象

     * @return 导出的文件对象的接口对象

     */

    protected abstract ExportFileApi factoryMethod();

}

(4)加入了两个Creator实现,先看看创建导出成文本文件格式的对象,示例代码如下:

 

 

/**

 * 具体的创建器实现对象,实现创建导出成文本文件格式的对象

 */

public class ExportTxtFileOperate extends ExportOperate{

    protected ExportFileApi factoryMethod() {

       //创建导出成文本文件格式的对象

       return new ExportTxtFile();

    }

}

再看看创建导出成数据库备份文件形式的对象,示例代码如下:

 

 

/**

 * 具体的创建器实现对象,实现创建导出成数据库备份文件形式的对象

 */

public class ExportDBOperate extends ExportOperate{

    protected ExportFileApi factoryMethod() {

       //创建导出成数据库备份文件形式的对象

       return new ExportDB();

    }

}

(5)客户端直接创建需要使用的Creator对象,然后调用相应的功能方法,示例代码如下:

 

 

public class Client {

    public static void main(String[] args) {

       //创建需要使用的Creator对象

       ExportOperate operate = new ExportDBOperate();

       //调用输出数据的功能方法

       operate.export("测试数据");

    }

}

运行结果如下:

 

 

导出数据测试数据到数据库备份文件

 

        你还可以修改客户端new的对象,切换成其它的实现对象,试试看会发生什么。看来应用工厂方法模式是很简单的,对吧。

 

 

 

 

未完待续...... 

 

    在大家的鼓励和支持下,《研磨设计模式》一书终于制作完成,即将与大家见面,现已进行预售,喜爱的朋友可以前往选购。

    预售链接:http://www.china-pub.com/197168  ,谢谢大家的支持!

    要是觉得这本书有用,记得在网店说几句好话啊,写书真的很辛苦,希望大家支持_^_

原创粉丝点击