定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步骤。

模板方法模式相对而言比较简单,一般的都是由抽象类定义好模板方法,

然后,子类通过继承并实现其父类中定义好的模板中需要执行的具体的方法,调用子类对象的模板方法时,会执行该类中的具体实现的方法。

这个模式我个人的感觉有点像是面向过程的操作,执行完一道工序,接着下一道工序。

类型:行为类模式

类图:

例子:

汽车厂造悍马

假设我们是一个汽车公司,现在有客户来了,要求我们造悍马! 既然上级下来命令那就造呗,但是造悍马你得告诉我们汽车有什么功能啊,客户说了:“能启动车,能停止车,能响,能跑。”好,功能出来了,开始造汽车了。类图如下:

抽象悍马模型代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<code>publicabstract class HummerModel {
    /*
    * 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
    * 是要能够发动起来,那这个实现要在实现类里了
    */
    publicabstract void start();
    //能发动,还要能停下来,那才是真本事
    publicabstract void stop();
    //喇叭会出声音,是滴滴叫,还是哔哔叫
    publicabstract void alarm();
    //引擎会轰隆隆地响,不响那是假的
    publicabstract void engineBoom();
    //那模型应该会跑吧,别管是人推的,还是电力驱动的,总之要会跑
    publicabstract void run();
}
</code>

在抽象类中,我们定义了悍马模型都必须具有的特质:能够发动、停止,喇叭会响,引擎可以轰鸣,而且还可以停止。但是每个型号的悍马实现是不同的。

H1型号的悍马代码下所示:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<code>publicclass HummerH1Model extendsHummerModel {
    //H1型号的悍马车鸣笛
    publicvoid alarm() {
        System.out.println("悍马H1鸣笛...");
    }
    //引擎轰鸣声
    publicvoid engineBoom() {
         System.out.println("悍马H1引擎声音是这样的...");
    }
    //汽车发动
    publicvoid start() {
        System.out.println("悍马H1发动...");
    }
    //停车
    publicvoid stop() {
        System.out.println("悍马H1停车...");
    }
    //开动起来
    publicvoid run(){
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
        this.alarm();
        //到达目的地就停车
        this.stop();
    }
}
</code>

大家注意看run()方法,这是一个汇总的方法,一个模型生产成功了,总要拿给客户检测吧,怎么检测?“是骡子是马,拉出去溜溜”,这就是一种检验方法,让它跑起来!通过run()这样的方法,把模型的所有功能都测试到了。

H2型号悍马如下代码所示。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<code>publicclass HummerH2Model extendsHummerModel {
    //H2型号的悍马车鸣笛
    publicvoid alarm() {
        System.out.println("悍马H2鸣笛...");
    }
    //引擎轰鸣声
    publicvoid engineBoom() {
        System.out.println("悍马H2引擎声音是这样在...");
    }
    //汽车发动
    publicvoid start() {
        System.out.println("悍马H2发动...");
    }
    //停车
    publicvoid stop() {
        System.out.println("悍马H2停车...");
    }
    //开动起来
    publicvoid run(){
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
        this.alarm();
        //到达目的地就停车
        this.stop();
    }
}
</code>

好了,程序编写到这里,已经发现问题了,两个实现类的run()方法都是完全相同的,那这个run()方法的实现应该出现在抽象类,不应该在实现类上,抽象是所有子类的共性封装。
注意 在软件开发过程中,如果相同的一段代码复制过两次,就需要对设计产生怀疑, 架构师要明确地说明为什么相同的逻辑要出现两次或更多次。
好,问题发现了,我们就需要马上更改,修改后的类图下所示:

注意 抽象类HummerModel中的run()方法,由抽象方法变更为实现方法,其源代码如下所示。<喎�"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMyBpZD0="修改后的抽象悍马模型">修改后的抽象悍马模型

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<code>publicabstract class HummerModel {
    /*
    * 首先,这个模型要能发动起来,别管是手摇发动,还是电力发动,反正
    * 是要能够发动起来,那这个实现要在实现类里了
    */
    publicabstract void start();
    //能发动,还要能停下来,那才是真本事
    publicabstract void stop();
    //喇叭会出声音,是滴滴叫,还是哔哔叫
    publicabstract void alarm();
    //引擎会轰隆隆地响,不响那是假的
    publicabstract void engineBoom();
    //那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑
    publicvoid run(){
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
        this.alarm();
        //到达目的地就停车
        this.stop();
    }
}
</code>

场景类实现的任务就是把生产出的模型展现给客户,场景类代码如下:

?
1
2
3
4
5
6
7
8
9
<code>publicclass Client {
    publicstatic void main(String[] args) {
        //XX公司要H1型号的悍马
        HummerModel h1 = newHummerH1Model();
        //H1模型演示
        h1.run();
    }
}
</code>

运行结果如下所示。

悍马H1发动… 悍马H1引擎声音是这样的… 悍马H1鸣笛…
悍马H1停车…

目前客户只要看H1型号的悍马车,没问题,生产出来,同时可以运行起来给他看看。 非常简单,那如果我告诉你这就是模板方法模式你会不会很不屑呢?就这模式,太简单了, 我一直在使用呀!是的,你经常在使用,但你不知道这是模板方法模式,那些所谓的高手就可以很牛地说:“用模板方法模式就可以实现”,你还要很崇拜地看着,哇,牛人,模板方法 模式是什么呀?这就是模板方法模式。

注意 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。

模板方法模式的使用场景

● 多个子类有公有的方法,并且逻辑基本相同时。

● 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个 子类实现。

● 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通 过钩子函数(见“模板方法模式的扩展”)约束其行为。

模板方法模式的扩展

目前为止,这两个模型都稳定地运行,突然有一天,领导急匆匆地找到了我:“看你怎么设计的,车子一启动,喇叭就狂响,吵死人了!客户提出H1型号的悍马喇叭 想让它响就响,H2型号的喇叭不要有声音,赶快修改一下。”
自己惹的祸,就要想办法解决它,稍稍思考一下,解决办法有了,先画出类图如下:

类图改动似乎很小,在抽象类HummerModel中增加了一个实现方法isAlarm,确定各个型 号的悍马是否需要声音,由各个实现类覆写该方法,同时其他的基本方法由于不需要对外提 供访问,因此也设计为protected类型。其源代码如下所示:

扩展后的抽象模板类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<code>publicabstract class HummerModel {
    /*
    * 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
    * 是要能够发动起来,那这个实现要在实现类里了
    */
    protectedabstract void start();
    //能发动,还要能停下来,那才是真本事
    protectedabstract void stop();
    //喇叭会出声音,是滴滴叫,还是哔哔叫
    protectedabstract void alarm();
    //引擎会轰隆隆的响,不响那是假的
    protectedabstract void engineBoom();
    //那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑
    finalpublic void run() {
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //要让它叫的就是就叫,喇嘛不想让它响就不响
        if(this.isAlarm()){
            this.alarm();
        }
        //到达目的地就停车
        this.stop();
    }
    //钩子方法,默认喇叭是会响的
    protected  boolean isAlarm(){
    returntrue;
    }
}
</code>

在抽象类中,isAlarm是一个实现方法。其作用是模板方法根据其返回值决定是否要响喇叭,子类可以覆写该返回值,由于H1型号的喇叭是想让它响就响,不想让它响就不响,由人控制,其源代码如下所示。

扩展后的H1悍马

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<code>publicclass HummerH1Model extendsHummerModel {
    privateboolean alarmFlag = true;  //要响喇叭
    protectedvoid alarm() {
        System.out.println("悍马H1鸣笛...");
    }
    protectedvoid engineBoom() {
        System.out.println("悍马H1引擎声音是这样的...");
    }
    protectedvoid start() {
        System.out.println("悍马H1发动...");
    }
    protectedvoid stop() {
        System.out.println("悍马H1停车...");
    }
    protectedboolean isAlarm() {
        returnthis.alarmFlag;
    }
    //要不要响喇叭,是由客户来决定的
    publicvoid setAlarm(booleanisAlarm){
        this.alarmFlag = isAlarm;
    }
}
</code>

只要调用H1型号的悍马,默认是有喇叭响的,当然你可以不让喇叭响,通过isAlarm(false)就可以实现。H2型号的悍马是没有喇叭声响的,其源代码如代码下所示。

扩展后的H2悍马

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<code>publicclass HummerH2Model extendsHummerModel {
    protectedvoid alarm() {
         System.out.println("悍马H2鸣笛...");
    }
    protectedvoid engineBoom() {
        System.out.println("悍马H2引擎声音是这样的...");
    }
    protectedvoid start() {
         System.out.println("悍马H2发动...");
    }
    protectedvoid stop() {
         System.out.println("悍马H2停车...");
    }
    //默认没有喇叭的
    protectedboolean isAlarm() {
        returnfalse;
    }
}
</code>

H2型号的悍马设置isAlarm()的返回值为false,也就是关闭了喇叭功能。场景类代码如下所示。

扩展后的场景类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<code>publicclass Client {
    publicstatic void main(String[] args) throwsIOException{
        System.out.println("-------H1型号悍马--------");
        System.out.println("H1型号的悍马是否需要喇叭声响?0-不需要   1-需要");
        String type=(newBufferedReader(newInputStreamReader(System.in))). HummerH1Model h1 = newHummerH1Model();
        if(type.equals("0")){
            h1.setAlarm(false);
        }
        h1.run();
        System.out.println("\n-------H2型号悍马--------");
        HummerH2Model h2 = newHummerH2Model();
        h2.run();
    }
}
</code>

运行是需要交互的,首先,要求输入H1型号的悍马是否有声音,如下所示:

?
1
2
3
4
5
6
<code>-------H1型号悍马-------- H1型号的悍马是否需要喇叭声响?0-不需要1-需要输入“0”后的运行结果如下所示:
-------H1型号悍马-------- H1型号的悍马是否需要喇叭声响?0-不需要1-需要
0
悍马H1发动... 悍马H1引擎声音是这样的... 悍马H1停车...
-------H2型号悍马-------- 悍马H2发动... 悍马H2引擎声音是这样的... 悍马H2停车...
</code>

输入“1”后的运行结果如下所示:

?
1
2
3
4
5
6
7
8
<code>-------H1型号悍马-------- H1型号的悍马是否需要喇叭声响?0-不需要1-需要   
1
悍马H1发动...
悍马H1引擎声音是这样的...
悍马H1鸣笛...
悍马H1停车...
-------H2型号悍马-------- 悍马H2发动... 悍马H2引擎声音是这样的... 悍马H2停车...
</code>

看到没,H1型号的悍马是由客户自己控制是否要响喇叭,也就是说外界条件改变,影响到模板方法的执行。在我们的抽象类中isAlarm的返回值就是影响了模板方法的执行结 果,该方法就叫做钩子方法(Hook Method)。有了钩子方法模板方法模式才算完美,大家 可以想想,由子类的一个方法返回值决定公共部分的执行结果,是不是很有吸引力呀!

模板方法模式就是在模板方法中按照一定的规则和顺序调用基本方法,具体到前面那个例子,就是run()方法按照规定的顺序(先调用start(),然后再调用engineBoom(),再调用 alarm(),最后调用stop())调用本类的其他方法,并且由isAlarm()方法的返回值确定run()中的 执行顺序变更。

模版方法模式的结构

模版方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,抽象类中的方法分为三种:

  • 抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。
  • 模版方法:由抽象类声明并加以实现。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。
  • 钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。
  • 抽象类的任务是搭建逻辑的框架,通常由经验丰富的人员编写,因为抽象类的好坏直接决定了程序是否稳定性。

实现类用来实现细节。抽象类中的模版方法正是通过实现类扩展的方法来完成业务逻辑。只要实现类中的扩展方法通过了单元测试,在模版方法正确的前提下,整体功能一般不会出现大的错误。

模版方法的优点及适用场景

容易扩展。一般来说,抽象类中的模版方法是不易反生改变的部分,而抽象方法是容易反生变化的部分,因此通过增加实现类一般可以很容易实现功能的扩展,符合开闭原则。

便于维护。对于模版方法模式来说,正是由于他们的主要逻辑相同,才使用了模版方法,假如不使用模版方法,任由这些相同的代码散乱的分布在不同的类中,维护起来是非常不方便的。

比较灵活。因为有钩子方法,因此,子类的实现也可以影响父类中主逻辑的运行。但是,在灵活的同时,由于子类影响到了父类,违反了里氏替换原则,也会给程序带来风险。这就对抽象类的设计有了更高的要求。

在多个子类拥有相同的方法,并且这些方法逻辑相同时,可以考虑使用模版方法模式。在程序的主框架相同,细节不同的场合下,也比较适合使用这种模式

【设计模式】—-(13)模板方法模式(行为型)相关推荐

  1. 设计模式之模板方法模式(行为型)

    文章目录 一.模式定义 二.模式角色 三.模式分析 四.具体例子 五.模式应用场景 一.模式定义 模板方法模式就是在一个抽象类中定义一些骨架方法,然后通过类继承的方法,将一些方法延迟到继承类里.模板方 ...

  2. Java设计模式之模板方法模式(UML类图分析+代码详解)

    大家好,我是一名在算法之路上不断前进的小小程序猿!体会算法之美,领悟算法的智慧~ 希望各位博友走过路过可以给我点个免费的赞,你们的支持是我不断前进的动力!! 加油吧!未来可期!! 本文将介绍java设 ...

  3. 设计模式之模板方法模式(TemplateMethod)

    设计模式之模板方法模式 模板方法模式 代码实现 模式的应用 模板方法模式 在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板.它的子类可以按需要重写方法实现 ...

  4. 设计模式-04.模板方法模式

    设计模式-04.模板方法模式 模板方法模式 定义 介绍 实现 优点 缺点 使用场景 扩展 定义 在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板.它的子类 ...

  5. 一篇博客读懂设计模式之---模板方法模式

    设计模式之模板方法模式: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤. 简而言之就是:父类定义了骨架(调用哪些方法及其 ...

  6. 设计模式之模板方法模式详解

    设计模式之模板方法模式详解 概述 在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的 ...

  7. php templete什么意思,PHP设计模式之模板方法模式定义与用法详解

    本文实例讲述了PHP设计模式之模板方法模式定义与用法.分享给大家供大家参考,具体如下: 什么是模板方法模式 模板方法(Template Method)设计模式中使用了一个类方法templateMeth ...

  8. 【设计模式】模板方法模式(C#)

    [设计模式]模板方法模式 1.概述 背景 在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某 ...

  9. 【Java】设计模式之模板方法模式

    - 好记性不如烂笔头,特将设计模式之模板方法模式记录在我的小仓库. 文章目录 什么是模板方法模式 案例 什么是模板方法模式 简而言之就是一套算法可以适用于多个类,则进行的步骤汇总,让子类别在不改变算法 ...

  10. Carson带你学设计模式:模板方法模式(Template Method)

    前言 今天Carson来全面总结最常用的设计模式 - 模板方法模式. Carson带你学设计模式系列文章 Carson带你学设计模式:这是一份全面 & 详细的设计模式学习指南 Carson带你 ...

最新文章

  1. Linux主机下无报错安装Apache服务器
  2. Docker的常用管理命令Docker将数据挂载到容器的三种方式
  3. Spring WebClient和Java日期时间字段
  4. Tomcat 服务:解决 Apache Tomcat 更新后 Tomcat9w.exe 无法启动 Tomcat 服务的问题
  5. 亚马逊出的平板电脑_美国最畅销的安卓平板电脑,还只有2GB内存
  6. linux线程wait和sleep,java多线程 sleep()和wait()的区别
  7. ICCV2021 |优胜劣汰,MIT团队提出自适应多模态选取框架用于视频理解
  8. Android 动态权限申请 BaseActivity 封装 拨打电话
  9. 内窥镜去反光的论文整理
  10. 花书+吴恩达深度学习(一)前馈神经网络(多层感知机 MLP)
  11. 《Windows核心编程》之七 - 关于Windows 2000中内存的分区
  12. VisionMaster 学习笔记(仪表盘检测)
  13. php qrcode 生成二维码 中间加logo的二维码
  14. Lyn for Mac v2.1 中文版 – 轻量级图片浏览器
  15. 高等数学——二重积分的计算方法
  16. 高兴就好,简单就好,明白就好
  17. 2013MDCC 参观有感
  18. linux 添加系统启动,怎样把这个linux系统添加到启动选项?
  19. 笔记本计算机的连接无线网络连接,计算机无法连接到无线网络,我将详细教您解决笔记本电脑无法连接到无线网络...
  20. docker容器网络配置之容器间的链接(默认桥接网络下的links)

热门文章

  1. 一句话脚本系列之获取eth0网卡的IP地址(或MAC地址)
  2. 提高执行力的五个法则
  3. 图形引擎实战:皮肤效果
  4. Linux中Shutdown命令实现定时自动关机
  5. 使用放射渐变制作光影效果
  6. mysql省市级联pid_sql全国 省市 联动级联
  7. SQLAlchemy:python数据库连接 神器
  8. 《2021年轻人熬夜报告》出炉~
  9. 二体问题之5:开普勒方程
  10. 爱迪生的复仇:直流电崛起