bboyjing's blog

HeadFirst设计模式九【模板方法模式】

这个模式光看名字,真的是不知所云,我看了下简介,意思是是深入封装算法。还记得第一个策略模式讲的就是算法族的封装。下面来学习一下,看看两者有什么异同。

场景描述

这里模拟了一个甜品店制作咖啡和茶的过程。制作咖啡的过程如下:

  1. 把水煮沸
  2. 用沸水冲泡咖啡
  3. 把咖啡倒进杯子
  4. 加糖和牛奶

制作茶的过程如下:

  1. 把水煮沸
  2. 用沸水浸泡茶叶
  3. 把茶叶倒进杯子
  4. 加柠檬

可以看出制作咖啡和茶的过程非常相似。下面就来实现这个场景。

代码示例

代码示例将从正常写法写法慢慢演变,来体会该模式的好处。

正常写法

  1. 构建咖啡类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Coffee {
    void prepareReceipe() {
    boilWater();
    brewCoffeeGrinds();
    pourInCup();
    addSugarAndMilk();
    }
    public void boilWater() {
    System.out.println("Boiling water");
    }
    public void brewCoffeeGrinds() {
    System.out.println("Dripping Coffee through filter");
    }
    public void pourInCup() {
    System.out.println("Pouring into cup");
    }
    public void addSugarAndMilk() {
    System.out.println("Adding Sugar and Milk");
    }
    }
  2. 构建茶类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class Tea {
    void prepareRecipe() {
    boilWater();
    steepTeaTag();
    pourInCup();
    addLemon();
    }
    public void boilWater() {
    System.out.println("Boiling water");
    }
    public void steepTeaTag() {
    System.out.println("Steeping the tee");
    }
    public void pourInCup() {
    System.out.println("pouring into cup");
    }
    public void addLemon() {
    System.out.println("Adding Lemon");
    }
    }

可以看出,普通的写法两个类差不多,有重复代码,下面看看模板方法模式如何来消除重复代码。

模板方法

从上面的代码可以看出boilWate()pourInCup()两者是一样的,很容易抽象。再仔细看下,它们的冲泡算法其实是一样的,只是有两个步骤使用的东西不一样,下面看下如何抽象。

  1. 定义咖啡因饮料超类,因为咖啡和茶叶都含有咖啡因,所以定义该超类是合理的

    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
    public abstract class CaffeeineBeverate {
    final void prepareRecipe() {
    boilWater();
    brew();
    pourInCup();
    addCondiments();
    }
    /**
    * 抽象冲泡方法,由子类实现
    */
    abstract void brew();
    /**
    * 抽象添加调味料,由子类实现
    */
    abstract void addCondiments();
    void boilWater() {
    System.out.println("Boiling water");
    }
    void pourInCup() {
    System.out.println("Pouring into cup");
    }
    }
  2. 定义咖啡

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Coffee extends CaffeeineBeverate {
    @Override
    void brew() {
    System.out.println("Dripping coffee through filter");
    }
    @Override
    void addCondiments() {
    System.out.println("Adding Sugar and Milk");
    }
    }
  3. 定义茶

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Tea extends CaffeeineBeverate {
    @Override
    void brew() {
    System.out.println("Stepping the tea");
    }
    @Override
    void addCondiments() {
    System.out.println("Adding lemon");
    }
    }
  1. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Test {
    public static void main(String[] args) {
    Coffee coffee = new Coffee();
    coffee.prepareRecipe();
    System.out.println();
    Tea tea = new Tea();
    tea.prepareRecipe();
    }
    }

从代码可以看出,CaffeeineBeverate定义了整体流程,子类只要将抽象方法实现即可,简单、清晰,确实忧于普通的写法。这里说的模板方法就是preareRecipe(),它定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。现在看来,这个跟策略模式的场景还是完全不一样的。学习的模式越来越多,越往后越容易混乱,所以要偶尔回顾回顾,理清每种模式的使用场景。书中列举了之前学的几种模式的使用场景区别:

  • 模板方法:子类决定如何实现算法中的步骤
  • 策略:封装可互换的行为,然后使用委托来决定要采用哪一个行为
  • 工厂方法:由子类决定实例化那个具体类

另外有一点要知晓下,工厂方法是模板方法的一种特殊版本。本章节就到这里了。