经过一段时间的疯狂加班,文章还要坚持写。本期我们来探讨模板方法模式及其常用场景。以及你可能分不清模板方法与策略模式的区别,没错,读完本文你肯定有收获。
原创声明:未经授权,不得转载,侵权必究,转载前请与作者取得联系。
读前思考
之前,笔者写了8篇设计模式的文章了,算来也不少了。写完策略模式,明白优先使用接口,而不是继承。在设计一个系统时,总是要我们考虑哪些是未来可能发生变化的部分,现有的设计能不能在未来变化时改动最小等等。后面写完装饰器模式,明白并非所有设计都优先使用继承,而是根据实际需要来使用继承或接口,只是接口更偏重点。在装饰器模式中,使用继承的目的是为了保证被装饰的对象始终是同一类型。而到了模板方法模式,你将会明白,我们的设计又是一半依赖继承,一半依赖接口的抽象,两者兼而有之,所以说设计这个工作真是博大精深,只有更合适,没有最好。
编程世界中的工作量问题
在计算机刚刚兴起时,计算机的主要职责是处理计算,大部分工作都是算法类的计算逻辑。一个算法会由固定的若干步骤构成,每个步骤相互独立,并按照顺序依次执行后得到最终结果。后来因现实世界需求的变更,算法的某个步骤中会根据需求,计算逻辑会有些不同,因此程序员需要用写两套算法程序来实现功能和需求。但这两套程序中,只有一个步骤计算逻辑不同,但程序员却把其他相同的步骤都要重写。在哪个没有高级编程语言的时代,重写很多一模一样的代码工作效率变的很差。随着面向对象高级编程语言的实现以及程序设计思想的成熟。模板方法模式诞生了,它允许程序员只针对固定可变的步骤重写算法逻辑,其他不变的步骤不需要重写就能完成整个算法的功能,是不是很酷。
模板方法模式怎么实现
请先在脑中思考下,利用继承,接口,组合,抽象等面向对象设计思想,怎么实现一个模板方法来完成上面重写算法中的一个步骤就可以实现功能了。
嘿嘿嘿,前言中提到,模板方法模式一半依赖继承,一半依赖抽象接口。想一想,是不是用抽象类可以实现。抽象类中的方法可以是具体的,也可以是抽象的(留给子类实现的),子类继承抽象类,只实现抽象方法,而不重写具体方法,是不是就达到了上面我们说的功能。
哈哈哈,读到这里,相信你已经知道怎么写代码了。别急,我们先看模板方法模式的定义。
模板方法定义:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤的逻辑。
模板方法UML类图
模板方法使用示例
我们有这样一个例子来实现,例如我们常见的接口实现逻辑大体总是分成如下几个步骤完成:
1、请求日志记录
2、参数合法性校验
3、执行业务方法并返回数据
针对不同的接口请求,每个接口的参数合法性校验和执行业务方法部分不同,需要单独重写,其他步骤可以不用改变。
定义模板父类:AbstractInterface
定义了一个抽象模板父类,父类的templateMethod定义了接口请求的算法步骤,并按顺序执行。另外三个方法:recordLog、checkParam、execute均为接口执行的三个步骤。
/**
* @author Misout
* @date 2018-09-09 18:10:21
*/
public abstract class AbstractInterface {
/** 执行日志记录 */
public void recordLog(BaseRequest request) {
System.out.println("收到业务ID[" + request.getBusinessId() + "]的请求");
}
/** 校验入参合法性 */
public abstract Result checkParam(BaseRequest request);
/** 执行业务逻辑方法 */
public abstract Result execute(BaseRequest request);
/** 接口请求模板方法,只用把这个方法对外部调用方暴露即可 */
public final Result templateMethod(BaseRequest request) {
// 算法步骤1:记录日志
recordLog(request);
// 算法步骤2:校验入参合法性
Result result = checkParam(request);
if(result.getCode().equals(ReturnCode.INVALID_PARAM)) {
return result;
}
// 算法步骤3:执行业务方法
result = execute(request);
// 返回数据
return result;
}
}
具体子类接口实现:UserInterface
用户接口子类,只重写了入参校验方法和执行业务方法
/**
* @author Administrator
* @date 2018-09-09 19:10:13
*/
public class UserInterface extends AbstractInterface {
@Override
public Result checkParam(BaseRequest request) {
UserRequest userRequest = (UserRequest)request;
if(StringUtils.isEmpty(userRequest.getUserName())) {
return new Result(ReturnCode.INVALID_PARAM, "userName is empty", null);
}
return new Result(ReturnCode.SUCCESS, "success", null);
}
@Override
public Result execute(BaseRequest request) {
UserRequest userRequest = (UserRequest)request;
// TODO : 根据userName查询用户信息逻辑。此处模拟成功查询
User user = new User(1L, "张三", 20, "男");
return new Result<User>(ReturnCode.SUCCESS, "success", user);
}
}
测试类
public class MainTest {
public static void main(String[] args) {
UserInterface userService = new UserInterface();
UserRequest request = new UserRequest();
// 暂且用时间毫秒数作为请求ID
request.setBusinessId(System.currentTimeMillis());
request.setUserName("");
// 非法请求
Result<User> result = userService.templateMethod(request);
System.out.println("返回结果:" + result.toString());
// 正常请求
request.setBusinessId(System.currentTimeMillis());
request.setUserName("张三");
result = userService.templateMethod(request);
System.out.println("返回结果:" + result.toString());
}
}
返回结果:
收到业务ID[1536492899546]的请求
返回结果:Result{code=1, msg='userName is not allowed null', data=null}
收到业务ID[1536492899553]的请求
返回结果:Result{code=0, msg='success',
data=User{userId=1, userName='张三', age=20, sex='男'}}
部分代码未贴出,如有需要请至作者GitHub代码库下载:https://github.com/Misout/DesignPattern,并查看Chapter08-TemplateMethod子模块项目的代码
模板方法模式 VS 策略模式
1、策略模式定义了一个算法家族,并让这些算法可以互换改变,每一个算法实现都被封装起来了,所以客户端可以很轻易的使用不同算法。
2、模板方法模式定义了一个算法的实现大纲和骨架结构,而由其子类实现其中某些步骤的内容,这么一来,模板方法在算法中的个别步骤可以有不同的实现细节,但算法的结构依然维持不变。这一点上,策略模式放弃了对算法骨架的控制权。
3、策略模式通过实现接口以及组合的方式进行算法的实现,可以让客户端选择不同的算法,而不是通过继承。模板方法通过继承和抽象方法实现对算法某一步骤的控制权。
4、通过模板方法和策略都可以达到代码的复用,以及算法的不同实现,就看实际应用场景因地选择哪种方式来设计系统了。
推荐阅读
设计模式(一)策略模式
设计模式(二)观察者模式
设计模式(三)装饰器模式
设计模式(四)简单工厂模式
设计模式(五)工厂方法模式
设计模式(六)抽象工厂模式
设计模式(七)单例模式你用对了吗
设计模式(八)适配器模式