打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
5分钟,开发出一个自己的框架 两大设计模式联袂主演

书接上文,我们继续上节课的案例,基于工厂系列模式(简单工厂,工厂方法,抽象工厂)打造可扩展的,支持多语言的电商平台店铺商品销售数据导出模块。

上一节,我们基于简单工厂模式和工厂方法模式,实现了案例的前三版代码。接下来,我们继续研究工厂方法模式。

完整视频

5分钟,开发出一个自己的框架 两大设计模式联袂主演

结论先行

根据REIS分析模型,对工厂方法模式进行分析,工厂方法模式包含五种角色,抽象工厂角色,具体工厂角色,抽象产品角色,具体产品角色,客户方角色。它的宗旨是,在抽象工厂的接口和抽象类里面,定义工厂方法,创建抽象产品。而将创建具体产品的操作,延迟到具体工厂的工厂方法中。它的目的是解除框架在创建对象时,对具体类依赖,实现了两者的解耦和。

基本思路

工厂方法(Factory method)模式定义

工厂方法(Factory method)模式-自产自销形态

案例需要复杂一点-导出格式完整的excel文件

第4版代码:Excel导出框架-基于工厂方法模式的自产自销形态

头脑风暴-为什么是个框架(Framework)

头脑风暴-为什么是自产自销形态

你是不是在糊弄我们?

使用REIS模型分析工厂方法模式

工厂方法模式的通用类图和代码

工厂方法(Factory method)模式定义

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

—— Gof《Design Patterns: Elements of Reusable Object-Oriented Software》

定义一个用于创建对象的接口,让子类决定实例化哪一个。Factory Method 使一个类的实例化延迟到其子类。

——Gof《设计模式:可复用面向对象软件的基础》

这个定义里面关键词,接口,子类,类。

在前面的简单工厂模式中,要决定创建对象的具体类型,也就是具体产品,是由工厂类中的静态工厂方法的入参,来决定的。而工厂方法模式中,决定创建对象的具体类型,则是由具体工厂类,也就是抽象工厂的子类来确定。

接口

这里的接口,是用于创建对象的,所以是工厂的接口。注意,这里面不仅仅是接口,往往伴随接口,还会建立相应的抽象类,抽象类一般都是当父类的,否则后面的子类,从何谈起呢。不论是接口,还是抽象类,都称为工厂模式里面的抽象工厂

子类

谁的子类,就是继承了上面的抽象工厂,我们刚刚提到的抽象父类的子类。当然,没有抽象父类也可以,子类直接执行抽象工厂的接口也是可以的,只不过再叫它子类就不合适了,而应该叫接口的实现类,这些都是面向对象的基础知识。

子类的一个重要任务,就是实现抽象工厂接口里面,定义的工厂方法,并且确定要返回哪个具体产品的对象,毕竟工厂方法的最终目的是为了生产产品,也就是创建对象。抽象工厂的子类,通常称为工厂模式里面的具体工厂

让子类决定实例化哪一个“类”,这里的“类”,就是被实例化,被创建的产品的类。在抽象工厂里面,工厂方法的返回值,通常是接口,这些接口在工厂模式里面,就被称为抽象产品。而在抽象工厂的子类,也就是具体工厂的工厂方法里面,返回的是一个真正的类,这个类,被称为具体产品

最后,在这个模式的定义里面,后面一句话,“使一个类的实例化延迟到其子类”,强调了工厂方法的核心思想,也就是它的宗旨,那就是将对象(产品)的实例化,延迟到抽象工厂的子类(具体工厂)来实现。这是它与简单工厂最根本的区别。简单工厂模式,决定创建哪个具体对象,是由工厂方法的入参来决定。

那为什么,必须要在子类中,来实例化对象呢,这就需要介绍工厂方法模式的另外一种形态,被我暂时称为自产自销形态。

工厂方法(Factory method)模式-自产自销形态

工厂方法模式的首选形态,我暂时命名为自产自销形态,要讲解这个形态,我们还需要从工厂方法这个设计模式的名字说起。大家仔细看,认真看,用几千倍的显微镜看,看工厂方法,这个设计模式的名字“factory method”,这个名字的第二个单词,method,我们在哪里见过呢?

翻遍23个设计模式,只有另外一个设计模式,那就是我前面已经分享的模板方法(Template method)模式,和工厂方法(factory method)模式,名字有点像,都是关于“method”的模式,也就是“方法”的模式。作者把这个设计模式命名为工厂方法(Factory method)模式,说明“方法”二字,在这个模式里面举足轻重,不可小觑。

工厂方法模式的自产自销形态,常常与模板方法模式联合使用。我们不玩虚的,先看代码,再总结理论

案例需要复杂一点-导出格式完整的excel文件

在开始编码之前,我们还需要把我们的案例需求完善一下,否则工厂方法模式的自产自销形态,英雄无用武之地,工厂方法模式和模板方法模式的联合演出,需要更大的舞台。

前面的需求中,我们只强调了excel导出类,可以导出excel文件,支持2003和2007版本,但是我们对导出的内容格式没有任何要求。对于一个excel格式的导出文件,贪得无厌的客户,或者反复无常的产品经理,提出下面的要求是合理的,也是合乎逻辑的(这让我想起了少林足球里面的经典台词,作为一名汽车修理工,随身携带一把扳手是合理的,也是合乎逻辑的)。

  1. 标题行:导出的excel加个标题,这个要求不过分吧,如:XXX自营旗舰店1月份商品销售数据
  2. 表头行:导出的表格,不能只有数据,还得有个像样的表头,如:商品ID,商品名称,一级类目ID等。
  3. 数据行:这个大家都能想到,不用多说。
  4. 汇总行:对导出的数据,增加个汇总行,这个要求也不过分吧。

需求有了,架构师和码农就得忙起来,我们根据上面的需求,重新设计我们的案例,并且要使用上工厂方法模式的自产自销形态,配合模板方法模式,实现一个excel文件的导出框架。

框架,框架,框架。

确实是一个框架,你没看错,仔细往下瞧。

第4版代码:Excel导出框架-基于工厂方法模式的自产自销形态

UML类图

接口与类

抽象工厂

IExcelExportFactory:抽象工厂-接口


AbstractExcelExportFactory:抽象工厂-抽象类

抽象产品

IExcelFile:抽象产品:excel文件

IFileTitleRow:抽象产品:文件标题行

ITableTitleRow:抽象产品:表头行

IDataRow:抽象产品:数据行

ITotalRow:抽象产品:汇总行

具体工厂

Excel2003ExportFactory:具体工厂

Excel2007ExportFactory:具体工厂

TestFileExport:测试类

IExcelExportFactory:抽象工厂-接口

package com.geekarchitect.patterns.factorymethod.demo04;import com.geekarchitect.patterns.factorymethod.demo03.SKU;import java.util.List;/** * 抽象工厂:接口 * * @author 极客架构师@吴念 * @createTime 2022/6/15 */public interface IExcelExportFactory { /** * 工厂方法 * * @return */ IExcelFile createExcel(); /** * 工厂方法 * * @return */ IFileTitleRow createFileTitleRow(); /** * 工厂方法 * * @return */ ITableTitleRow createTableTitleRow(); /** * 工厂方法 * * @return */ IDataRow createDataRow(); /** * 工厂方法 * * @return */ ITotalRow createTotalRow(); /** * 模板方法模式:模板方法 * * @param skuList */ void exportExcel(List<SKU> skuList);}

AbstractExcelExportFactory:抽象工厂-抽象类

package com.geekarchitect.patterns.factorymethod.demo04;import com.geekarchitect.patterns.factorymethod.demo03.SKU;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.List;/** * 抽象工厂:抽象类 * @author 极客架构师@吴念 * @createTime 2022/6/15 */public abstract class AbstractExcelExportFactory implements IExcelExportFactory {    private static final Logger LOG = LoggerFactory.getLogger(AbstractExcelExportFactory.class);    /*     *Excel文件     * @author: 极客架构师@吴念     * @date: 2022/6/18     * @param:     * @return:     */    protected IExcelFile excelFile = null;    /**     * 模板方法模式:个性化方法,同时也是钩子方法     */    @Override    public IFileTitleRow createFileTitleRow() {        //可以提供一个默认实现,创建文件标题行对象,并添加到excel文件中。。。        LOG.info('抽象工厂:创建文件标题行对象');        return null;    }    /**     * 模板方法模式:个性化方法,同时也是钩子方法。     */    @Override    public IExcelFile createExcel() {        //可以提供一个默认实现,比如创建一个excel2003对应的workbook。        LOG.info('抽象工厂:创建Excel文件对象,默认为2003版本');        return new IExcelFile() {            @Override            public void addFileTitleRow(IFileTitleRow fileTitleRow) {            }            @Override            public void addTableTitleRow(ITableTitleRow tableTitleRow) {            }            @Override            public void addDataRow(IDataRow dataRow) {            }            @Override            public void addTotalRow(ITotalRow totalRow) {            }        };    }    /**     * 模板方法模式:模板方法     *     * @param skuList     */    @Override    public final void exportExcel(List<SKU> skuList) {        LOG.info('抽象工厂-模板方法:导出Excel文件');        excelFile = createExcel();        IFileTitleRow fileTitleRow = createFileTitleRow();        ITableTitleRow tableTitleRow = createTableTitleRow();        IDataRow dataRow = createDataRow();        ITotalRow totalRow = createTotalRow();        excelFile.addFileTitleRow(fileTitleRow);        excelFile.addTableTitleRow(tableTitleRow);        excelFile.addDataRow(dataRow);        excelFile.addTotalRow(totalRow);    }}


IExcelFile:抽象产品:excel文件

package com.geekarchitect.patterns.factorymethod.demo04;/** * 抽象产品:接口 * @author 极客架构师@吴念 * @createTime 2022/6/18 */public interface IExcelFile { void addFileTitleRow(IFileTitleRow fileTitleRow); void addTableTitleRow(ITableTitleRow tableTitleRow); void addDataRow(IDataRow dataRow); void addTotalRow(ITotalRow totalRow);}

IFileTitleRow:抽象产品:文件标题行

package com.geekarchitect.patterns.factorymethod.demo04;/** * 抽象产品 * @author 极客架构师@吴念 * @createTime 2022/6/18 */public interface IFileTitleRow {    /**     * 获取标题长度     *     * @return     */    int getTitleLength();    /**     * 获取未格式化的标题内容     *     * @return     */    String getRawContent();}

ITableTitleRow:抽象产品:表头行

package com.geekarchitect.patterns.factorymethod.demo04;/** * 抽象产品 * @author 极客架构师@吴念 * @createTime 2022/6/18 */public interface ITableTitleRow { int getColumnSize(); String getColumnTitle(int columnIndex);}

IDataRow:抽象产品:数据行

package com.geekarchitect.patterns.factorymethod.demo04;import com.geekarchitect.patterns.factorymethod.demo03.SKU;import java.util.List;/** * 抽象产品 * @author 极客架构师@吴念 * @createTime 2022/6/18 */public interface IDataRow {    int getRowCount();    List<SKU> getData();}

ITotalRow:抽象产品:汇总行

package com.geekarchitect.patterns.factorymethod.demo04;/** * 抽象产品 * @author 极客架构师@吴念 * @createTime 2022/6/18 */public interface ITotalRow { int getSKUCount();}

Excel2003ExportFactory:具体工厂

package com.geekarchitect.patterns.factorymethod.demo04;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 具体工厂: * @author 极客架构师@吴念 * @createTime 2022/6/15 */public class Excel2003ExportFactory extends AbstractExcelExportFactory {    private static final Logger LOG = LoggerFactory.getLogger(Excel2003ExportFactory.class);    @Override    public ITableTitleRow createTableTitleRow() {        LOG.info('具体工厂:创建表头对象');        return null;    }    @Override    public IDataRow createDataRow() {        LOG.info('具体工厂:创建数据对象');        return null;    }    @Override    public ITotalRow createTotalRow() {        LOG.info('具体工厂:创建汇总行对象');        return null;    }}

Excel2007ExportFactory:具体工厂

package com.geekarchitect.patterns.factorymethod.demo04;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 具体工厂 * @author 极客架构师@吴念 * @createTime 2022/6/15 */public class Excel2007ExportFactory extends AbstractExcelExportFactory { private static final Logger LOG = LoggerFactory.getLogger(Excel2007ExportFactory.class); @Override public IExcelFile createExcel() { LOG.info('具体工厂:创建Excel2007对象'); return new IExcelFile() { @Override public void addFileTitleRow(IFileTitleRow fileTitleRow) { } @Override public void addTableTitleRow(ITableTitleRow tableTitleRow) { } @Override public void addDataRow(IDataRow dataRow) { } @Override public void addTotalRow(ITotalRow totalRow) { } }; } @Override public ITableTitleRow createTableTitleRow() { LOG.info('具体工厂:创建表头对象'); return null; } @Override public IDataRow createDataRow() { LOG.info('具体工厂:创建数据对象'); return null; } @Override public ITotalRow createTotalRow() { LOG.info('具体工厂:创建汇总行对象'); return null; }}

TestFileExport:测试类

package com.geekarchitect.patterns.factorymethod.demo04;import com.geekarchitect.patterns.factorymethod.demo03.AbstractTest;import com.geekarchitect.patterns.factorymethod.demo03.SKU;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.List;/** * @author 极客架构师@吴念 * @createTime 2022/6/13 */public class TestFileExport extends AbstractTest {    private static final Logger LOG = LoggerFactory.getLogger(TestFileExport.class);    public static void main(String[] args) {        TestFileExport testFileExport = new TestFileExport();        testFileExport.demo01();    }    public void demo01() {        LOG.info('第四版代码:Excel文件导出框架-基于工厂方法模式自产自销形态');        List<SKU> skuList = generateSku(100);        IExcelExportFactory excelExportFactory = new Excel2003ExportFactory();        excelExportFactory.exportExcel(skuList);        excelExportFactory = new Excel2007ExportFactory();        excelExportFactory.exportExcel(skuList);    }}

运行结果

头脑风暴-为什么是个框架(Framework)

什么是框架(Framework),对于程序员,特别是java程序员,不知道女朋友姓名不丢人,不知道什么是框架,问题就很严重了。自从学习java语言第一天开始,我们就整天和框架打交道。项目是用Spring框架搭建的,访问数据库要用mybatis或者hibernate框架,表示层要用struts框架(这个已经没落了)或者spring web框架。学习了这些框架,后面还有一堆框架等着我们,直到我们的头发掉光为止。

要完整的讲清楚框架的一切,说三天三夜也说不完,后面有机会,我们再细聊,会有这一天的。我们今天只强调几个框架的特点就可以了。

1,框架的服务对象是程序员。

框架不是业务系统,不能直接给最终客户使用,而是给程序员用的。它是一个半成品,程序员要在此基础上,开发自己的业务系统,从而提高自己的开发效率,否则就得自己造轮子。

2,框架往往通过接口或者抽象父类,约定规则和流程,提供默认解决方案,而子类或者具体类则需要由使用框架的程序员实现。

我们刚刚开发的excel导出功能,就符合这些特点,排除掉Excel2003ExportFactory和Excel2007ExportFactory这两个类,其他的抽象工厂和抽象产品,就是一堆接口和抽象类,是一个半成品,并不能真的导出Excel文件。

实际工作中,由一拨程序员开发前面的抽象工厂和抽象产品,然后给另一拨程序员开发具体工厂。当然,这两拨人,也有可能是同一拨人马,虽然概率比较低。

第一拨人马:被称为架构师,或者高级开发工程师,高手都是定义规则的,玩虚的。

以下接口及类,就是他们开发的。

抽象工厂:

IExcelExportFactory:抽象工厂-接口


AbstractExcelExportFactory:抽象工厂-抽象类

抽象产品:

IExcelFile:抽象产品:excel文件

IFileTitleRow:抽象产品:文件标题行

ITableTitleRow:抽象产品:表头行

IDataRow:抽象产品:数据行

ITotalRow:抽象产品:汇总行

规则在哪里,就在抽象工厂里面定义的工厂方法里,就在抽象类实现的个别工厂方法里(提供默认解决方法),就在上面这五个抽象产品的接口里面。

流程在哪里,就在抽象工厂的抽象类中,实现的模板方法里(模板方法的主要作用就是定义流程)。

规则和流程定义的好不好,符不符合业务需求,扩展性强不强,就是体现架构师功力的地方。有网友问,什么是架构师,当你开始定义规则,当你开始关注接口和抽象类,当你开发的代码,是给其他程序员提供支持和服务时,你就踏上了架构师之路。

第二拨人马:一般就是普通程序员了,要按照规则解决具体问题。

需要继承上面的
AbstractExcelExportFactory抽象类,实现自己的具体类,如。

Excel2003ExportFactory:具体工厂

Excel2007ExportFactory:具体工厂

这些都符合我们上面提到的,框架的两个特点,所以,我们已经开发了一个自己的框架,基于工厂方法模式和模板方法模式的框架。虽然功能比较简单,但确实是名副其实的导出excel文件的框架。

那为什么是自产自销形态呢?

头脑风暴-为什么是自产自销形态

从抽象工厂:IExcelExportFactory接口和
AbstractExcelExportFactory抽象类的代码,我们可以看到。它里面定义的五个工厂方法,都是给它们里面的模板方法exportExcel()方法使用的。自己的方法创建对象给自己的方法用,所以说它是自产自销,应该没毛病,这个名字是我暂定的名字,还没有最终确定下来。

IExcelFile createExcel();IFileTitleRow createFileTitleRow();ITableTitleRow createTableTitleRow();IDataRow createDataRow();ITotalRow createTotalRow();

你是不是在糊弄我们?

码农老吴,你是不是在糊弄我们,是不是为了流量,故弄玄虚,哗众取宠,别的设计模式的书籍,为啥很少这么讲。

确实是,我参考的设计模式书籍,除了GOF的原著,还有三四本国内的设计模式书籍,只有一本讲了这种形态。其他的几本书,基本上都是我前面分享的工厂方法模式的普通形态。我估计,可能是涉及到框架开发知识,实际的业务项目中,很少涉及,所以讲的就比较少吧。

Gof,设计模式的开山鼻祖,在他们的著作《Design Patterns: Elements of Reusable Object-Oriented Software》中,对于工厂方法模式和框架(Framework)之间的关系,有如下论述。

1,Frameworks use abstract classes to define and maintain relationships between objects. A framework is often responsible for creating these objects as well.

2,This creates a dilemma: The framework must instantiate classes, but it only knows about abstract classes, which it cannot instantiate.

3,The Factory Method pattern offers a solution. It encapsulates the knowledge of which Document subclass to create and moves this knowledge out of the framework.

第一句:框架基于抽象类来定义和维护对象之间的关系,框架也有创建对象的职责。

第二句:这就出现了一个两难的局面,框架需要创建类的对象,但是框架确只知道抽象类,而抽象类无法创建对象。

第三句:工厂方法模式,提供了一种解决方案。它将创建具体的子类的操作,移出到框架之外(由抽象工厂类的子类,来完成实例化)。

下面我们用REIS分析模式,对工厂方法模式进行深度,全方位的解析。

使用REIS模型分析工厂方法模式

REIS分析模型主要包括场景(scene),角色(role),交互(interaction),效果(effect)四个要素

场景(Scene)

场景,也就是我们在什么情况下,遇到了什么问题,需要使用某个设计模式。

当出现以下情况时,可能需要使用工厂方法模式。

1,一个工厂类无法预知它将要创建的产品对象对应的类,比如开发框架时,只有抽象类,没有具体类。

2,一个工厂类通过它自己的子类,决定需要创建的对象所属的类,也就是让子类做决策。

角色(Role)

角色,一般为设计模式出现的类,或者对象。每种角色有自己的职责。

工厂方法模式,有五类角色,抽象工厂角色,具体工厂角色,抽象产品角色,具体产品角色,客户方角色。

抽象工厂角色(Abstract Factory role)

负责在工厂接口或者抽象类中,定义工厂方法。工厂方法的返回值为抽象产品。

具体工厂角色(Concrete Factory role)

继承抽象工厂对应的抽象类,实现抽象工厂里面定义的工厂方法。在工厂方法里面,根据业务场景,返回具体产品角色的对象。

抽象产品角色(Abstract Product role)

定义抽象工厂角色里面的工厂方法,返回的产品。一般为接口,也可以是抽象类(比较少见),毕竟现在都是面向接口编程。

具体产品(Concrete Product role)

执行抽象产品接口的实现类,由具体工厂角色里面的工厂方法,返回具体产品的对象。

在工厂方法模式的自产自销形态,开发框架时,可以没有具体产品,而由使用框架的程序员实现。

客户方角色(Client role)

在工厂方法模式的普通形态里面,客户方角色,是使用工厂方法的类。

而在工厂方法模式的自产自销形态,客户方角色,通常是抽象工厂里面定义的模板方法。

交互(interaction)

交互,是指设计模式中,各种角色是如何交互的,一般用UML中的序列图,活动图来表示。简单的说就是角色之间是如何配合,完成设计模式的使命的。

普通形态

工厂客户方角色,根据需要,实例化相应的具体工厂类,然后调用它里面的工厂方法,获取所需的对象。

自产自销形态

抽象工厂里面定义的模板方法,充当工厂客户方角色,调用抽象工厂里面定义的工厂方法,而这些方法,需要由抽象工厂的子类,也就是具体工厂来实现。

效果(effect)

效果,使用该设计模式之后,达到了什么效果,有何意义,当然,也可以说说它的缺点,或者风险。

工厂方法模式的效果如下:

工厂方法模式,效果很多,但是最重要的,就是解除了抽象类创建对象时,对具体类的依赖,实现了两者的解耦合。

工厂方法模式,我们讲解完毕,总结一下。

根据REIS分析模型,对工厂方法模式进行分析,工厂方法模式包含五种角色,抽象工厂角色,具体工厂角色,抽象产品角色,具体产品角色,客户方角色。它的宗旨是,在抽象工厂的接口和抽象类里面,定义工厂方法,创建抽象产品。而将创建具体产品的操作,延迟到具体工厂的工厂方法中。它的目的是解除框架在创建对象时,对具体类依赖,实现了两者的解耦和。

工厂方法模式的通用类图和代码

类图-普通形态

类图-自产自销形态

相关代码,已上传到github上,大家自行下载即可。

简单工厂模式,工厂方法模式,已经讲完了,工厂系列模式里面的重量级选手,抽象工厂就要上场了。

未完待续。。。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
GoF设计模式之三 Factory Method- -
关于程序一些看发
简单工厂模式和工厂方法模式
设计模式之工厂模式(五)
JAVA设计模式之工厂模式
【重温设计模式】之004抽象工厂模式
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服