前言
本系列文章主要介绍日常工作中用得较多的设计模式如:代理模式、装饰模式、适配器模式、桥接模式、模板方法模式、责任链模式、策略模式,命令模式、状态模式、这九种模式在工作中比较常见。而创建型模式如原型模式,工厂方法模式,单例模式,建造者模式这四种是必须学会的,创建型模式结合结构型,行为型模式才能解决实际问题。以这些设计模式为引子,打开编程思路。从其原理,关键理解点,相似模式及在何种场景下选择适当的设计来解决实际业务问题,以解放初步程序员,规范业务流程实现及代码,提升整体开发效率从而缩短软件的开发周期。
软件开发中采用软件设计模式并不是目标,在具体的软件幵发中,必须根据应用系统的特点和要求来恰当选择,不能强行采用设计模式,那样会适得其反。对于简单的程序开发,一个简单的算法可能比引入设计模式要效果更好。而大型项目开发采用设计模式来组织代码显然更为高效,更高质量。
在本系列文章中将要反复提及面向对象设计七个原则。因为面向对象设计原则是设计模式的基石,抛开23种设计模式,根据实际情况编写满足七个原则中的3至4个原则的代码,完成后回顾代码结构时你会发现可以在23种模式中找到最类似的设计模式。
本文目录
面向对象设计七个原则
- 单一职责原则:设计功能单一的类。
- 开放-封闭原则:对扩展开放,对修改封闭
- 依赖倒置原则:针对接口编码,不要针对实现编码,要依赖于抽象,而非具体实现
- 接口隔离原则:使用多个专门(单一功能)的接口,而不使用多种功能的总接口(单一职责)
- 李氏替换原则:子类可以替换父类(依赖倒置)
- 组合重用原则:能用组合的情况下,绝不使用继承关系来重用。
- 最少知识原则(迪米特原则):一个对象应当对其他的对象不关心其内部细节。
第一个编程中常用的设计模式:模板方法模式
概念
模板方法模式(行为型模式):定义一个算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重新定义算法的某些特定步骤。
这个怎么理解呢,这个好比有序列表。
- 步骤----我起床
- 步骤----我刷牙
- 步骤----我洗脸
- 步骤----我还没想好做什么,总之我做完,第五才能开始做
- 步骤----好可怜的我,天天等4
根据需求分析,好几种业务步骤都比较固定,那么可以定好一些处理步骤1,2,3,4,5,这些处理步骤是固定的,已被父类安排好步骤的顺序。1,2,3,5步骤让父类实现,唯独4是变化的,让子类(不同的业务)实现。
记忆关键点
牢记------步骤骨架 另一种说法是“算法骨架”
步骤骨架:需要程序员在充分分析业务的情况,重组多个业务流程,找出这些业务的共同点,将业务流程固定化成步骤,将变化的步骤留待子类实现。
设计类图
学习目标
只要出现有共同特征的固定步骤的几种业务,马上想起步骤骨架,进而在实现业务时采用模板方法模式设计程序结构
与哪些模式相似,如何区分及应用
工厂方法模式:一个创建对象的方法,由子类实现。
区别:工厂是创建对象,模板方法模式是子类实现个别步骤。
应用:可以用工厂方法模式来创建模板方法模式的对象。
策略模式:定义算法,让调用者无关乎算法实现。
区别:策略模式定义算法,模板方法模式定义有序步骤。
应用:模板方法的某些步骤可通过策略模式实现。
注意这里的相似是看起来相似,它们与模板方法模式没有半毛钱关系。
策略模式或工厂是对应一组对象中选择一个单一对象来执行业务
模板方法模式是将未完成的步骤补全
扩展联想
与微服务的编排,工作流中的流程是不是有些许相似?将步骤做成一个个微服务,通过服务编排最后实现业务,进而形成小中台的概念。
实例
-
前述
在2017年,我被调配到某财务对账项目,支援其开发工作。项目经理对财务报表模块开发人员的进展及质量非常不满意,希望我能对这部分的程序进行重构。经过对原代码为期一周的阅读与分析,基本有个大致的解决方案,下面说一下这个模块的需求 -
场景需求
- 需要为财务人员提供商品订单数据与支付数据及银行进财数据的财务报表
- 财务报表多达40多份,并随业务的扩展,需要提供更多的报表
- 报表的格式不定,可自定义
- 报表的数据量比较大一个excel页可达10多万行
- 报表生成时,因excel的行数限制,需生成多个文件,并打包上传至指定FTP服务器
- 因数据量大,不能采用表达式语言工具进行模板文件形式生成excel(性能非常低),只能将一行的数据转化为数组形式。
- 调用者类(客户端Service类)与被调用者类30多个(报表业务Service类)形成互调用环
- 每一个报表业务Service类主要有三项工作
一、处理分页参数,并查询数据
二、将查询数据转为对应excel文件行的数组String[]
三、采用spring注入形式调用客户端Service类,使用它的excel文件生成方法和FTP上传方法 - 客户端Service类,采用spring注入形式调用报表业务Service类,每增加一个新的报表需求,就增加一项业务Service类注入。
- 执行时客户端Service类通过参数来判断具体执行哪个报表业务Service类,也就是
if-else
形式
原代码类结构图
//客户端Service类
@Service
public class ReportFTPDownloadServiceImpl{
@Resource
private TicketHelperServiceImpl ticketHelperService;
@Resource
private ReconCompleteDetailOrderToGatewayServiceImpl reconCompleteDetailOrderToGatewayService;
@Resource
private PaymentHelperServiceImpl paymentHelperService;
@Resource
private ReportHandlerServiceN nReportService;
//省略了实现代码......
public void queryByTypeData(Object query, String fileType, String templeFileName, ExportFile entity)
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, ParseException {
// 网关数据
if (fileType.equals(GlobalConstan.UPDLOAD_FILE_TYPE.get(10).toString())) {
gatewayDataService.queryExportData(query, templeFileName, entity);
}
//
if (fileType.equals("国内线下退款".toString()) || fileType.equals("国际线下退款".toString())) {
offlineRefundService.queryExportData(query, templeFileName, entity);
}
//
if (fileType.equals("差错销售".toString()) || fileType.equals("国际差错销售".toString())) {
mistakeDataService.queryExportData(query, templeFileName, entity);
}
//
if (fileType.equals("MASK".toString()) || fileType.equals("国际MASK".toString())) {
maskDataService.queryExportData(query, templeFileName, entity);
}
// 销售数据-支付
if (fileType.equals(GlobalConstan.UPDLOAD_FILE_TYPE.get(16).toString())) {
ecsOrderDomesticSellDataSellVoService.queryExportData(query, templeFileName, entity);
}
///以下还有20几个if...
}
}
//其中一个报表业务Service类
@Service
public class TicketHelperServiceImpl {
@Resource
private ReportFTPDownloadServiceImpl reportFtpDownload;
//省略了实现代码......
}
//另一个报表业务Service类
@Service
public class PaymentHelperServiceImpl {
@Resource
private ReportFTPDownloadServiceImpl reportFtpDownload;
//省略了实现代码......
}
- 采用模板方法模式,将每一个报表业务service类的公共方法提取出来放入到抽象类中,将数据查询方法设计成抽象方法,由子类实现。抽象类的方法有
一、分页处理方法
二、抽象的数据查询方法
三、对象数据转换为数组方法,这个方法使用JAVA的反射机制 - 新建一个excel文件生成工具类,在指定目录中生成excel文件
- 客户端Service类采用spring的泛型注入方法,注入所有的报表业务Service类。
//客户端Service类
@Resource
public class ReportFTPDownloadServiceImpl{
@Autowired
private Map<String, AbstractReportExcelFileBuilder> exportFileBuilders;
//省略了实现代码......
public void queryByTypeData(Object query, String fileType, String templeFileName, ExportFile entity)
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, ParseException {
AbstractReportExcelFileBuilder reportBuilder = exportFileBuilders.get(fileType);
reportBuilder.doExcelHandler(entity);
}
}
@Service
public abstract class AbstractReportExcelFileBuilder{
public void doExcelHandler(ExportFile entity){
//省略处理分页数据....
for (int i = 1; i <= pageNumber; i++) {
List<Object> datas = queryData();
List<String[]> excelDatas = convertExportFileData(datas );
//调用ExcelFileUtil生成文件
}
}
protected abstract List<Object> queryData();
protected List<String[]> convertExportFileData(List<?> datas){
//这里的代码是用java的反射机制将对象转成数组
}
}
通过代码结构的重构,解决了业务多变带来的麻烦,也进一步规范了程序员的发散性思维,同时减少了后继开发成本,提高了效率。
通常情况下,在业务中采用一个设计模式未必能完成业务的设计,它经常需要结合两至三个设计模式来解决一个业务问题。
这个实例结合了工厂模式与模板方法模式。你可能会问工厂模式,我在实例里没有看到啊。你可别忘了spring框架本身就是一个工厂容器。
回顾
好啦,我们来看看今天讲了哪些内容:
- 讲述了模板方法模式的概念
- 记忆关键点:
步骤骨架
- 与其他模式的相似性比较,区分
- 一个模板设计模式的类结构简图
- 一份真实案例
模板方法模式,你今天学会了吗?
系列文章《设计模式应用实例二(代理模式的应用)》