设计模式(九)模板方法

经过一段时间的疯狂加班,文章还要坚持写。本期我们来探讨模板方法模式及其常用场景。以及你可能分不清模板方法与策略模式的区别,没错,读完本文你肯定有收获。
原创声明:未经授权,不得转载,侵权必究,转载前请与作者取得联系。

读前思考

之前,笔者写了8篇设计模式的文章了,算来也不少了。写完策略模式,明白优先使用接口,而不是继承。在设计一个系统时,总是要我们考虑哪些是未来可能发生变化的部分,现有的设计能不能在未来变化时改动最小等等。后面写完装饰器模式,明白并非所有设计都优先使用继承,而是根据实际需要来使用继承或接口,只是接口更偏重点。在装饰器模式中,使用继承的目的是为了保证被装饰的对象始终是同一类型。而到了模板方法模式,你将会明白,我们的设计又是一半依赖继承,一半依赖接口的抽象,两者兼而有之,所以说设计这个工作真是博大精深,只有更合适,没有最好。

编程世界中的工作量问题

在计算机刚刚兴起时,计算机的主要职责是处理计算,大部分工作都是算法类的计算逻辑。一个算法会由固定的若干步骤构成,每个步骤相互独立,并按照顺序依次执行后得到最终结果。后来因现实世界需求的变更,算法的某个步骤中会根据需求,计算逻辑会有些不同,因此程序员需要用写两套算法程序来实现功能和需求。但这两套程序中,只有一个步骤计算逻辑不同,但程序员却把其他相同的步骤都要重写。在哪个没有高级编程语言的时代,重写很多一模一样的代码工作效率变的很差。随着面向对象高级编程语言的实现以及程序设计思想的成熟。模板方法模式诞生了,它允许程序员只针对固定可变的步骤重写算法逻辑,其他不变的步骤不需要重写就能完成整个算法的功能,是不是很酷

模板方法模式怎么实现

请先在脑中思考下,利用继承,接口,组合,抽象等面向对象设计思想,怎么实现一个模板方法来完成上面重写算法中的一个步骤就可以实现功能了。

嘿嘿嘿,前言中提到,模板方法模式一半依赖继承,一半依赖抽象接口。想一想,是不是用抽象类可以实现。抽象类中的方法可以是具体的,也可以是抽象的(留给子类实现的),子类继承抽象类,只实现抽象方法,而不重写具体方法,是不是就达到了上面我们说的功能。
哈哈哈,读到这里,相信你已经知道怎么写代码了。别急,我们先看模板方法模式的定义。

模板方法定义:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中某些步骤的逻辑。

模板方法UML类图

模板方法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、通过模板方法和策略都可以达到代码的复用,以及算法的不同实现,就看实际应用场景因地选择哪种方式来设计系统了。

推荐阅读

设计模式(一)策略模式
设计模式(二)观察者模式
设计模式(三)装饰器模式
设计模式(四)简单工厂模式
设计模式(五)工厂方法模式
设计模式(六)抽象工厂模式
设计模式(七)单例模式你用对了吗
设计模式(八)适配器模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,030评论 5 464
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,198评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,995评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,973评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,869评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,766评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,967评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,599评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,886评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,901评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,728评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,504评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,967评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,128评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,445评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,018评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,224评论 2 339

推荐阅读更多精彩内容