设计模式(三):模板方法模式

一、星巴克服务员

1.初级服务员

假如你是一位刚入职的星巴克服务员,负责为客人泡制咖啡和茶。公司规定茶和咖啡的泡制要遵循下面的步骤:

于是你按照步骤单上的要求设计了咖啡(Coffee)和茶(Tea)并进行了制作

*****************************Coffee*****************************

/**
 * 咖啡
 * @author wuqi
 * @Date 2019/2/12 17:31
 */
public class Coffee {
    /**
     *  准备咖啡
     */
    public void prepareRecipe(){
        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");
    }
}

*****************************Tea*****************************

/**
 * 茶
 * @author wuqi
 * @Date 2019/2/12 17:36
 */
public class Tea {
    /**
     * 准备茶
     */
    public void prepareRecipe(){
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    /**
     * 将水煮沸
     */
    public void boilWater() {
        System.out.println("Boiling water");
    }

    /**
     * 用沸水浸泡茶叶
     */
    public void steepTeaBag(){
        System.out.println("Steeping the tea");
    }

    /**
     * 将茶倒进杯子里
     */
    public void pourInCup(){
        System.out.println("Pouring into cup");
    }

    /**
     * 加柠檬
     */
    public void addLemon(){
        System.out.println("Adding Lemon");
    }
}

*****************************制作茶和咖啡*****************************

/**
 * 没有使用模板方法模式测试
 * @author wuqi
 * @Date 2019/2/12 17:44
 */
public class NoTemplateTest {
    public static void main(String[] args) {
        System.out.println("Making coffee...");
        Coffee coffee = new Coffee();
        coffee.prepareRecipe();

        System.out.println();
        System.out.println("Making Tea...");
        Tea tea = new Tea();
        tea.prepareRecipe();
    }
}

 

制作结果:

你成功的将咖啡制作了出来,但是一个高级服务员从你身旁经过时看了一眼你的制作代码,嘲讽的说到:你这样制作的咖啡和茶有太多的重复代码,如果以后再让你制作另一种饮料,制作的步骤应该还是类似的,你还要再把重复的代码写一遍,最要命的是你竟然没有对这些饮料进行抽象。你虽然不高兴,但是别人把你设计的代码存在的问题一一指了出来,你还是比较感激的。你恭敬的说到:你说的对,我会改进一下的。

2.高级服务员

根据之前嘲讽你的高级服务员的指导,看着你之前所写的代码,你陷入了沉思:首先,我应该将咖啡和茶抽象出来,这两种饮料都含有咖啡因,就抽象成CaffeineBeverage吧。然后咖啡和茶有两个完全相同的方法boilWater和pourIntoCup,将这两个方法放在抽象类中。这还不够,对于prepareRecipe()来说,咖啡和茶都是遵循着相同的步骤,而且是定死的步骤,不允许我们修改,只是步骤具体实现的方式不一样,这些方式是由具体的类(咖啡和茶)决定的,所以我可以将这个方法放在抽象类中作为一个模板(template),并且不允许子类重写,子类只可以重写某些步骤具体的实现方式,就是怎么泡饮料,怎么加调料,这样的话我就需要将咖啡的brewCoffeeGrinds()和steepTeaBag()泛化成brew()这个名字,都是冲泡,让子类具体去决定怎么个泡法,而对于咖啡的addSugarAndMilk()和茶的addLemon()则可以泛化成addCondiments(),就是加调料,具体加什么调料也由子类去决定。按照这个思路你画出了相关的UML类图

按照这个UML类图,你写下了改进后的代码:

*****************************CaffeineBeverage*****************************

/**
 * 咖啡因饮料
 * @author wuqi
 * @Date 2019/2/12 17:49
 */
public abstract class CaffeineBeverage {

    /**
     * 抽象类定义模板,定义准备咖啡因饮料的具体步骤
     * 该步骤为final,不允许子类修改
     */
    public final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        addCondiment();
    }

    /**
     * 将水煮沸
     */
    public void boilWater() {
        System.out.println("Boiling water");
    }

    /**
     * 冲泡
     */
    public abstract void brew();

    /**
     * 将饮料倒进杯子里
     */
    public void pourInCup(){
        System.out.println("Pouring into cup");
    }

    /**
     * 加调料
     */
    public abstract void addCondiment();
}

*****************************Coffee*****************************

/**
 * 咖啡
 * @author wuqi
 * @Date 2019/2/12 17:54
 */
public class Coffee extends CaffeineBeverage {
    /**
     * 用水冲泡咖啡
     */
    @Override
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    /**
     * 添加糖和牛奶
     */
    @Override
    public void addCondiment() {
        System.out.println("Adding Sugar and Milk");
    }
}

*****************************Tea****************************

/**
 * 茶
 * @author wuqi
 * @Date 2019/2/12 17:56
 */
public class Tea extends CaffeineBeverage {
    /**
     * 用水浸泡茶叶
     */
    @Override
    public void brew() {
        System.out.println("Steeping the tea");
    }

    /**
     * 添加柠檬
     */
    @Override
    public void addCondiment() {
        System.out.println("Adding Lemon");
    }
}

*****************************测试制作咖啡和茶****************************

/**
 * 模板方法测试
 * @author wuqi
 * @Date 2019/2/12 17:58
 */
public class TemplateTest {
    public static void main(String[] args) {
        System.out.println("Making Coffee...");
        Coffee coffee = new Coffee();
        coffee.prepareRecipe();

        System.out.println();
        System.out.println("Making Tea...");
        Tea tea = new Tea();
        tea.prepareRecipe();
    }
}

运行结果:

运行改进后的代码,我们依然成功制作了咖啡和茶。但是改进后的代码更加简洁了,复用度更高,而且以后如果再多出一个咖啡因饮料让我们去制作,我们只需要让新加的类继承CaffeineBeverage,并重写brew()和addCondiments方法即可,再也不用每次都添加很多重复的代码了,这样就使我们的系统非常的具有弹性。你非常的满意,看着改进后的代码,你突然想到这不就是之前我学习设计模式中的模板方法模式吗!想不到在这里用到了。这次,你老板从你身旁经过,看着你制作咖啡的代码,满意的点了点头,说到:不错,你这个咖啡制作的代码非常好,你用到了模板方法模式,现在开始,我正是聘用你为高级服务员了。你非常诧异也非常高兴的说了声:谢谢老板!随后你老板又说到:我们的茶后面不准备再加调料了,正好模板方法的钩子可以派上用场,你去改进一下吧,也当作你作为高级服务员的最后一道考核。你恭敬的回了句:是的!老板!

3.神奇的钩子

你努力的回忆着模板方法模式的知识,模板方法模式就是在抽象的基类中定义了一个模板,这个模板中规定了完成该方法所需的具体算法,对应于我们制作饮料的各个步骤,子类需要按照这个步骤去执行该方法,步骤中的一些实现方式可以延迟到子类去决定。而钩子是穿插在算法当中的一个可选的部分,它有默认的实现,钩子可以由子类实现并决定是否启用。你又画了个UML类图来表示加上钩子后的饮料制作方式。

 

根据这个UML类图,你写下了加上钩子的代码

 *****************************CaffeineBeverage****************************

/**
 * 咖啡因饮料
 * @author wuqi
 * @Date 2019/2/12 17:49
 */
public abstract class CaffeineBeverage {
    /**
     * 抽象类定义模板,定义准备咖啡因饮料的具体步骤
     * 该步骤为final,不允许子类修改
     */
    public final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        if(needAddCondiments()){
            addCondiment();
        }
    }

    /**
     * 钩子
     * @return
     */
    protected boolean needAddCondiments(){
        return false;
    }

    /**
     * 将水煮沸
     */
    public void boilWater() {
        System.out.println("Boiling water");
    }

    /**
     * 冲泡
     */
    public abstract void brew();

    /**
     * 将饮料倒进杯子里
     */
    public void pourInCup(){
        System.out.println("Pouring into cup");
    }

    /**
     * 加调料
     */
    public abstract void addCondiment();
}

 *****************************Coffee****************************

/**
 * 咖啡
 * @author wuqi
 * @Date 2019/2/12 17:54
 */
public class Coffee extends CaffeineBeverage {
    /**
     * 用水冲泡咖啡
     */
    @Override
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    /**
     * 添加糖和牛奶
     */
    @Override
    public void addCondiment() {
        System.out.println("Adding Sugar and Milk");
    }

    /**
     * 子类覆盖钩子
     * @return
     */
    @Override
    public boolean needAddCondiments() {
        return true;
    }

}

*****************************Tea****************************

/**
 * 茶
 * @author wuqi
 * @Date 2019/2/12 17:56
 */
public class Tea extends CaffeineBeverage {
    /**
     * 用水浸泡茶叶
     */
    @Override
    public void brew() {
        System.out.println("Steeping the tea");
    }

    /**
     * 添加柠檬
     */
    @Override
    public void addCondiment() {
        System.out.println("Adding Lemon");
    }
}

*****************************TemplateTest****************************

/**
 * 模板方法测试
 * @author wuqi
 * @Date 2019/2/12 17:58
 */
public class TemplateTest {
    public static void main(String[] args) {
        System.out.println("Making Coffee...");
        Coffee coffee = new Coffee();
        coffee.prepareRecipe();

        System.out.println();
        System.out.println("Making Tea...");
        Tea tea = new Tea();
        tea.prepareRecipe();
    }
}

 

测试结果:

可以看到现在制作茶已经不会再添加柠檬了,因为模板中钩子默认的是不加调料的,而Tea中没有重写钩子,所以茶现在是不加调料了。你把改进后的代码交给老板看,你老板非常满意的点了点头。从此,你开心的做起了一名高级服务员。

 

二、定义模板方法

1.模板方法的概念

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

2.UML类图

 

 3.模板方法模式中存在的OO设计原则

1.好莱坞原则

模板方法模式中用到OO设计原则中的好莱坞原则,别调用(打电话给)我们,我们会(打电话给)调用你。

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

2.模板方法模式和好莱坞原则

模板方法模式就是在抽象基类中定义了一个模板,规定了子类执行的具体步骤和时序,子类只是决定某些步骤的实现方式。抽象基类是高层,子类是低层,这刚好符合好莱坞原则。

 

三、模板方法模式的应用场景

模板方法模式适用需要定义一套固定的模板,模板中的算法是不更改的,但是某些具体的实现方式可以由子类决定的场景

四、模板方法模式的优缺点

1.优点

  1. 模板方法模式通过抽象类,可以将代码的复用最大化
  2. 具体的算法只存在于模板中,让系统的修改变得容易,系统会比较有弹性

2.缺点

       需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象。

 

posted @ 2019-02-13 15:58  安静的boy  阅读(513)  评论(0编辑  收藏  举报