书接上文,我们继续上节课的案例,基于工厂系列模式(简单工厂,工厂方法,抽象工厂)打造可扩展的,支持多语言的电商平台店铺商品销售数据导出模块。
上一节,我们基于简单工厂模式和工厂方法模式,实现了案例的前三版代码。接下来,我们继续研究工厂方法模式。
根据REIS分析模型,对工厂方法模式进行分析,工厂方法模式包含五种角色,抽象工厂角色,具体工厂角色,抽象产品角色,具体产品角色,客户方角色。它的宗旨是,在抽象工厂的接口和抽象类里面,定义工厂方法,创建抽象产品。而将创建具体产品的操作,延迟到具体工厂的工厂方法中。它的目的是解除框架在创建对象时,对具体类依赖,实现了两者的解耦和。
工厂方法(Factory method)模式定义
工厂方法(Factory method)模式-自产自销形态
案例需要复杂一点-导出格式完整的excel文件
第4版代码:Excel导出框架-基于工厂方法模式的自产自销形态
头脑风暴-为什么是个框架(Framework)
头脑风暴-为什么是自产自销形态
你是不是在糊弄我们?
使用REIS模型分析工厂方法模式
工厂方法模式的通用类图和代码
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”,这个名字的第二个单词,method,我们在哪里见过呢?
翻遍23个设计模式,只有另外一个设计模式,那就是我前面已经分享的模板方法(Template method)模式,和工厂方法(factory method)模式,名字有点像,都是关于“method”的模式,也就是“方法”的模式。作者把这个设计模式命名为工厂方法(Factory method)模式,说明“方法”二字,在这个模式里面举足轻重,不可小觑。
工厂方法模式的自产自销形态,常常与模板方法模式联合使用。我们不玩虚的,先看代码,再总结理论
在开始编码之前,我们还需要把我们的案例需求完善一下,否则工厂方法模式的自产自销形态,英雄无用武之地,工厂方法模式和模板方法模式的联合演出,需要更大的舞台。
前面的需求中,我们只强调了excel导出类,可以导出excel文件,支持2003和2007版本,但是我们对导出的内容格式没有任何要求。对于一个excel格式的导出文件,贪得无厌的客户,或者反复无常的产品经理,提出下面的要求是合理的,也是合乎逻辑的(这让我想起了少林足球里面的经典台词,作为一名汽车修理工,随身携带一把扳手是合理的,也是合乎逻辑的)。
需求有了,架构师和码农就得忙起来,我们根据上面的需求,重新设计我们的案例,并且要使用上工厂方法模式的自产自销形态,配合模板方法模式,实现一个excel文件的导出框架。
框架,框架,框架。
确实是一个框架,你没看错,仔细往下瞧。
接口与类
抽象工厂
IExcelExportFactory:抽象工厂-接口
AbstractExcelExportFactory:抽象工厂-抽象类
抽象产品
IExcelFile:抽象产品:excel文件
IFileTitleRow:抽象产品:文件标题行
ITableTitleRow:抽象产品:表头行
IDataRow:抽象产品:数据行
ITotalRow:抽象产品:汇总行
具体工厂
Excel2003ExportFactory:具体工厂
Excel2007ExportFactory:具体工厂
TestFileExport:测试类
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);}
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); }}
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);}
package com.geekarchitect.patterns.factorymethod.demo04;/** * 抽象产品 * @author 极客架构师@吴念 * @createTime 2022/6/18 */public interface IFileTitleRow { /** * 获取标题长度 * * @return */ int getTitleLength(); /** * 获取未格式化的标题内容 * * @return */ String getRawContent();}
package com.geekarchitect.patterns.factorymethod.demo04;/** * 抽象产品 * @author 极客架构师@吴念 * @createTime 2022/6/18 */public interface ITableTitleRow { int getColumnSize(); String getColumnTitle(int columnIndex);}
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();}
package com.geekarchitect.patterns.factorymethod.demo04;/** * 抽象产品 * @author 极客架构师@吴念 * @createTime 2022/6/18 */public interface ITotalRow { int getSKUCount();}
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; }}
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; }}
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),对于程序员,特别是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分析模型主要包括场景(scene),角色(role),交互(interaction),效果(effect)四个要素。
场景,也就是我们在什么情况下,遇到了什么问题,需要使用某个设计模式。
当出现以下情况时,可能需要使用工厂方法模式。
1,一个工厂类无法预知它将要创建的产品对象对应的类,比如开发框架时,只有抽象类,没有具体类。
2,一个工厂类通过它自己的子类,决定需要创建的对象所属的类,也就是让子类做决策。
角色,一般为设计模式出现的类,或者对象。每种角色有自己的职责。
工厂方法模式,有五类角色,抽象工厂角色,具体工厂角色,抽象产品角色,具体产品角色,客户方角色。
抽象工厂角色(Abstract Factory role)
负责在工厂接口或者抽象类中,定义工厂方法。工厂方法的返回值为抽象产品。
具体工厂角色(Concrete Factory role)
继承抽象工厂对应的抽象类,实现抽象工厂里面定义的工厂方法。在工厂方法里面,根据业务场景,返回具体产品角色的对象。
抽象产品角色(Abstract Product role)
定义抽象工厂角色里面的工厂方法,返回的产品。一般为接口,也可以是抽象类(比较少见),毕竟现在都是面向接口编程。
具体产品(Concrete Product role)
执行抽象产品接口的实现类,由具体工厂角色里面的工厂方法,返回具体产品的对象。
在工厂方法模式的自产自销形态,开发框架时,可以没有具体产品,而由使用框架的程序员实现。
客户方角色(Client role)
在工厂方法模式的普通形态里面,客户方角色,是使用工厂方法的类。
而在工厂方法模式的自产自销形态,客户方角色,通常是抽象工厂里面定义的模板方法。
交互,是指设计模式中,各种角色是如何交互的,一般用UML中的序列图,活动图来表示。简单的说就是角色之间是如何配合,完成设计模式的使命的。
普通形态
工厂客户方角色,根据需要,实例化相应的具体工厂类,然后调用它里面的工厂方法,获取所需的对象。
自产自销形态
抽象工厂里面定义的模板方法,充当工厂客户方角色,调用抽象工厂里面定义的工厂方法,而这些方法,需要由抽象工厂的子类,也就是具体工厂来实现。
效果,使用该设计模式之后,达到了什么效果,有何意义,当然,也可以说说它的缺点,或者风险。
工厂方法模式的效果如下:
工厂方法模式,效果很多,但是最重要的,就是解除了抽象类创建对象时,对具体类的依赖,实现了两者的解耦合。
工厂方法模式,我们讲解完毕,总结一下。
根据REIS分析模型,对工厂方法模式进行分析,工厂方法模式包含五种角色,抽象工厂角色,具体工厂角色,抽象产品角色,具体产品角色,客户方角色。它的宗旨是,在抽象工厂的接口和抽象类里面,定义工厂方法,创建抽象产品。而将创建具体产品的操作,延迟到具体工厂的工厂方法中。它的目的是解除框架在创建对象时,对具体类依赖,实现了两者的解耦和。
类图-普通形态
类图-自产自销形态
相关代码,已上传到github上,大家自行下载即可。
简单工厂模式,工厂方法模式,已经讲完了,工厂系列模式里面的重量级选手,抽象工厂就要上场了。
未完待续。。。
联系客服