模板方法模式

之前所学习的模式都是围绕着封装进行,如对象创建、方法调用、复杂接口的封装等,这次的模板方法模式将深入封装算法块,好让子类可以在任何时候都将自己挂接进运算里。

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

模板方法就是一个固定步骤的“算法”骨架方法。这个算法的可变部分通过继承,在子类中重载实现。这样就可以在算法骨架不变的情况下,算法细节步骤根据不同的需求进行适应的改变。

例题:茶饮店的饮品冲泡程序(泡茶与泡咖啡)

1

2

3

4

5

6

泡茶:                                             |            咖啡:

                                                  |

1. 煮沸水                                          |              1.煮沸水         

2. 加入茶叶冲泡                                     |              2.加入咖啡粉冲泡

3. 根据需求加入调料(如蜂蜜、柠檬)                   |              3.根据需求加入调料(如牛奶、糖)

4. 将泡好的茶水倒入杯子                              |              4.将泡好的咖啡倒入杯子

我们发现两者的步骤非常相似,仅有部分细节不一:如泡茶冲的是茶叶,加的是蜂蜜;泡咖啡加的是牛奶其实泡茶和泡咖啡的过程就是一个固定骨架步骤的“算法”,我们可以抽象为:

  1. 煮沸水
  2. 冲泡
  3. 根据需求加入调料
  4. 将泡好的饮料倒入杯子

斜体部分为算法中不一样的部分,如何解决?下面,我们用“模板方法模式”来解决这种不一致。

首先,定义一个含有固定骨架“模板方法”的咖啡因饮料抽象类:

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

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

/**

 *咖啡因饮料

 */

public abstract class CaffeineBeverage {

    /**

     *模板方法,准备饮料

     */

    public final void prepareRecipe(){

        boilWater();

        brew();

        //用于模板方法的算法中可选部分的控制

        if(customerWantsCondiment())

            addCondiment();

        pourInCup();

    }

    /**

     *煮沸水

     */

    public void boilWater() {

        System.out.println("煮沸水");

    }

    /**

        *冲泡

     */

    public abstract void brew();

    /**

     *增加调味剂

     */

    public abstract void addCondiment();

    /**

      *将饮料倒入杯子

     */

    public void pourInCup() {

        System.out.println("将饮料倒入杯中");

    }

    /**

     *“钩子”方法。顾客决定是否加调料

     */

    public Boolean customerWantsCondiment(){

        return true;

    }

}

这时,准备饮料的四个固定步骤我们都写在模板方法prepareRecipe()里了。这个算法步骤是不可更改的,所以我们给这个模板方法加了final关键字。
然后,根据茶和咖啡在算法步骤上的不同,我们设计两个类,继承抽象方法,分别重载模板方法中的步骤,从而实现茶和咖啡在算法步骤中各自的不同:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

public class Tea extends CaffeineBeverage{

    @Override

    public void brew() {

        System.out.println("浸泡茶叶");

    }

    @Override

    public void addCondiment() {

        System.out.println("添加蜂蜜");

    }

}

public class Coffee extends CaffeineBeverage{

    @Override

    public void brew() {

        System.out.println("冲泡咖啡粉");

    }

    @Override

    public void addCondiment() {

        System.out.println("添加糖和牛奶");

    }

}

测试:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public class Main {

    public static void main(String[] args) {

        CaffeineBeverage tea = new Tea();

        tea.prepareRecipe();

        System.out.println("===============");

        CaffeineBeverage coffee = new Coffee();

        coffee.prepareRecipe();

    }

}

/**输出:

 煮沸水

 浸泡茶叶

 添加蜂蜜

 将饮料倒入杯中

 ===============

 煮沸水

 冲泡咖啡粉

 添加糖和牛奶

 将饮料倒入杯中

*/

可以看到,通过继承,模板方法在茶和咖啡中的实现有了差别。
模板方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这样可以确保算法的结构保持不变,同时由子类提供部分的实现。
所以,模板方法就是定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
最后,我们给出模板方法的类图:

看上去模板方法似乎就这样结束了,然而,在上面定义的抽象类中还有一个“钩子(hook)”方法,
什么是“钩子”方法呢?我们来看一下定义:
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。

在上面的代码中,我们写了一个钩子来决定是否加调料

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

/**

 * “钩子”方法。顾客决定是否加调料

 */

public Boolean customerWantsCondiment(){

    return true;

}

/**

 * 模板方法,准备饮料

 */

public final void prepareRecipe(){

    boilWater();

    brew();

    //用于模板方法的算法中可选部分的控制

    if(customerWantsCondiment())

        addCondiment();

    pourInCup();

}

现在,我们用一个子类来挂钩:

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

32

33

34

public class Tea extends CaffeineBeverage{

    @Override

    public void brew() {

        System.out.println("浸泡茶叶");

    }

    @Override

    public void addCondiment() {

           System.out.println("添加蜂蜜");

    }

    //覆盖父类的“钩子”方法,更改算法中的可选部分

    @Override

    public Boolean customerWantsCondiment(){

        //询问顾客是否需要调料

        String answer = askCustomerNeedCondiment();

        if("y".equals(answer))

            return true;

        else

            return false;

    }

    private String askCustomerNeedCondiment() {

        String answer = null;

        System.out.println("请问您要不要加蜂蜜?请回答y或n");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        try {

            answer = in.readLine();

        catch (IOException e) {

            e.printStackTrace();

        }

        return answer;

    }

}

这时,我们准备茶水时就能根据顾客的回答而安排需要加调料这一步骤了。子类通过覆盖钩子方法,实现了算法中的可选部分。

模板方法模式中还使用到了一个新的设计原则:好莱坞原则
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你

好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖低层组件时,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。
在好莱坞原则之下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用我们,我们会调用你”。

好莱坞原则就是确保不会出现高层组件依赖底层组件、底层组件又依赖高层组件的“依赖腐败”。只有高层组件会决定什么时候和怎样使用底层组件,而底层组件不会调用高层组件


在模板方法模式中,算法的实现会调用到具体子类的某个方法,也就是高层组件依赖于底层组件。具体子类不会调用父类中的方法,不会形成底层组件依赖高层组件的环状依赖:

采用好莱坞原则的设计模式还有:工厂方法(可以看作特殊的模板方法),观察者、装饰者...

好莱坞原则和依赖倒置原则的关系:依赖倒置原则是尽量避免使用具体类,多使用抽象。

策略模式与模板方法模式

策略模式和模板方法模式很像,都是针对算法改变的情况的设计模式。以下是它们的区别:

  • 策略模式是采用的组合来实现算法的变化,这样的设计更加灵活,依赖性程度低;
  • 模板方法模式采用的继承来实现算法中的变化部分,这样的设计对算法有更多的控制权,且代码的重复会少一些,但由于算法依赖于父类,所以依赖程度高。

Java API中的模板方法

Java中较常见的模板方法模式的应用:

  1. java.io的InputStream类有一个read()方法,是由子类实现的,而这个方法又会被read(byte b[], int off, int len)模板方法使用。
  2. Swing的JFrame继承了一个paint()方法。在默认状态下,paint()是不做事情的,因为它是一个“钩子”。通过覆盖paint(),可以将自己的代码插入JFrame的算法中,显示出想要的画面。
  3. applet是一个能在网页上面执行的小程序。任何applet必须继承自Applet类,而Applet类中提供了好些钩子。
  4. Java数组排序方法,如Arrays.sort(Object[]);Object对象实现了Comparable接口,排序通过Comparable接口中的compareTo()实现。

注意,这个排序的例子表面看上去好像与模板方法模式无关(没有用到继承),但实质仍是通过子类提供算法步骤的实现来实现了算法的变化。虽然采用了组合,但思想仍是模板方法模式的思想。

总结

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

  • 模板方法定义了算法的步骤,把这些步骤实现延迟到子类。
  • 模板方法为我们提供了一种代码复用的重要技巧。
  • 模板方法的抽象类可以定义具体的方法、抽象方法和钩子方法。
  • 抽象方法由子类实现。
  • 钩子是一种方法,它在抽象类中不做事,或者只做默认的事,子类可以选择要不要覆盖它。
  • 好莱坞原则告诉我们将决策权放在高层模板中,以便决定如何及何时调用底层模块。
  • 策略模式和模板方法模式都封装算法,一个是用组合,一个是用继承。
  • 工厂方法是模板方法的一种特殊版本

《Head First设计模式》第八章笔记-模板方法模式相关推荐

  1. 《Head First 设计模式》读书笔记——模板方法模式

    模板方法模式. 案例 泡茶和冲咖啡的步骤很相似,咖啡的步骤: 把水煮沸 用沸水冲泡咖啡 把咖啡倒进杯子里 加糖和牛奶 泡茶的步骤: 把水煮沸 用沸水浸泡茶叶 把茶倒进杯子 加柠檬 所以咖啡的代码如下: ...

  2. 行为型设计模式(2)—— 模板方法模式(Template Method Pattern)

    文章目录 1.概述 2.简单实现 3.使用场景和优缺点 4.小结 参考文献 1.概述 使用设计模式可以提高代码的可复用性.可扩充性和可维护性. 模板方法模式(Template Method Patte ...

  3. java 模板方法_设计模式(java实现)_模板方法模式(Template method)

    设计模式(java实现)_模板方法模式(Template method) 模板方法模式是编程中经常用到到的模式.它定义了一个操作中的算法骨架,将某些步骤延迟到子类中实现.这样,新的子类可以在不改变一个 ...

  4. python设计模式【7】-模板方法模式

    UML类图简介 设计模式的分类 面向对象的设计原则 python设计模式[1]-单例模式 python设计模式[2]-工厂模式 python设计模式[3]-门面模式 python设计模式[4]-代理模 ...

  5. 设计模式笔记--模板方法模式

    常用设计模式有23中,分为: 创建型模式(主要用于创建对象) 1.单例模式    2.工厂方法模式    3.抽象工厂模式    4.建造者模式     5.原型模式  行为型模式 (主要用于描述对象 ...

  6. 设计模式读书笔记-----模板方法模式

    首先我们先来看两个例子:冲咖啡和泡茶.冲咖啡和泡茶的基本流程如下: 所以用代码来创建如下: 咖啡:Caffee.java public class Coffee {void prepareRecipe ...

  7. Delphi 设计模式:《HeadFirst设计模式》Delphi7代码---模板方法模式之CoffeineBeverageWithHook[转]...

    模板方法模式定义了一个算法骨架,允许子类对算法的某个或某些步骤进行重写(override).   1   2{<HeadFirst设计模式>之模板方法模式 }   3{ 编译工具: Del ...

  8. C++设计模式之Template Method(模板方法模式)

    模板方法模式总结起来就是灵活运用C++的多态性,灵活运用普通函数,虚函数,纯虚函数的组合, 打个比方,每个人每天的生活都不一样,老师要上课,学生要学习,程序员要编程,但是每个人都需要吃饭与休息,这样我 ...

  9. 大话设计模式(8)模板方法模式

    一.场景及UML图 场景:有点类似于原型模式,不过侧重点不一样.模板方法重点是体现在代码复用.把一些重复性的机制代码全部抽离到父类. 二.概念 模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延 ...

最新文章

  1. JAVA大一新生要用电脑吗,大一新生有没有必要买电脑?辅导员:倘若不是这三点原因尽量别买...
  2. a different object with the same identifier value was already associated with the session:
  3. 关于angular的$resource中的isArray属性问题
  4. 项目经理应该把30%的时间用在编程上
  5. linux 内核配置raid,在 Linux VM 上配置软件 RAID - Azure Virtual Machines | Microsoft Docs
  6. java 大特性_java三大特性
  7. 漂亮的页面过渡动画源码
  8. java图片去掉文字,Java 移除html,图片 链接转文字
  9. 原理简单,但不知道怎么用?一文看懂「同期群模型」
  10. Firefox 67不能勾选“以后自动采用相同的动作处理此类文件”解决方案
  11. chrome样式不生效_Chrome开发者工具的11个使用技巧
  12. Axure资源及原型工具Axure RP 9下载
  13. 信息安全原理与技术第七次实验:木马攻击与防范
  14. 【评价模型】模糊综合评价法 _数学建模 续更
  15. Astronauts UVALive - 3713(2-SAT)
  16. 转:为什么你不是真正的快乐
  17. Python爬取猫眼电影榜单评分,以及评论
  18. 计算机网络之应用层(DNS域名系统)
  19. 关于日程权限、黄历App功能使用流程
  20. 阿里python认证_集成阿里云滑动验证(python)

热门文章

  1. oracle对某两列求和再求和_函数实战:多列条件求和
  2. 下列python语言、返回结果不是uc_MKAN1-UC 5103作业代写、代做Analytics作业、Java,Python,c/c++程序语言作业代做...
  3. python socket发包_一个python发包的脚本
  4. idea怎么将本地文件和远程git对比_IDEA新建本地项目关联远程git仓库
  5. C#的变迁史07 - C# 4.0 之线程安全集合篇
  6. 查看mysql数据库的死锁日志_【MySQL】mysql死锁以及死锁日志分析
  7. python @修饰符_python函数修饰符@的使用方法解析
  8. Python3 爬虫学习笔记 C07 【解析库 lxml】
  9. 联想记忆计算机网络,什么是双向联想记忆神经网络
  10. 【CSU - 1980 】不堪重负的树(树上区间dp)