Back
Featured image of post 设计模式-模板方法

设计模式-模板方法

一、模式简介

说一些题外话,以前写博客都是将模式的详解和代码示例分开,写了几篇后发现将模式详解和代码穿插着更加有利于阅读理解。今天学习的设计模式是模板方法,模板方法是一个十分优秀且比较常用的设计模式,在许多开源的框架中都能够找到它的影子。模板方法模式:模板方法在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中。模板方法使得子类在不改变算法结构的情况下,重新去定义算法中的某些步骤

二、模式详解

下面我们还是以一个例子来引出模板方法模式,现在有一个制作饮料的方法叫prepareRecipe()。

现在我们来制作一杯咖啡在方法中就有如下步骤:1、把水煮沸 2、制作咖啡 3、把咖啡倒入杯子 4、在咖啡内加入适当调料。

再来制作一杯茶有如下步骤:1、把水煮沸 2、制作茶 3、把茶倒入杯子 4、在茶中加入适当调料。

通过上述制造两种饮料的步骤我们发现,制作这两种饮料步骤1和步骤3是一样的,只不过可能在步骤2和4中因为不用的饮料制作步骤和加入调料会有所不同。但是制造出这两种饮料的整体制作步骤却是完全相同的。因此我们可以通过模板方法来解决这一问题,我们先来看看模板方法的类图:

通过模板方法的类图,对于上边的示例我们发现制造这两种饮料的步骤是一致的,因此可以定义一个模板方法来定义这个"制作饮料"的算法骨架,而在制作和添加调料的步骤不相同,因此我们可以将这两个方法抽象出来让具体的子类来实现各自的步骤。

  • 饮料类(父类中通过模板方法定义骨架)
/**
 * 饮料
 *
 */
public abstract class Beverage {
    /**
     * 准备饮料
     * 模板方法,定义算法骨架,因此设置为final子类不该修改
     */
    final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    /**
     * 制作
     */
    abstract void brew();

    /**
     * 添加调味品
     */
    abstract void addCondiments();

    /**
     * 煮水
     */
    void boilWater(){
        System.out.println("将水烧开");
    }

    /**
     * 倒入杯子
     */
    void pourInCup(){
        System.out.println("将饮料倒入杯子");
    }
}
  • 咖啡(实现自己需要的步骤)
/**
 * 咖啡
 *
 */
public class Coffee extends Beverage{
    @Override
    void brew() {
        System.out.println("制作咖啡");
    }

    @Override
    void addCondiments() {
        System.out.println("加入牛奶");
    }
}
  • 茶(实现自己需要的步骤)
/**
 * 茶
 *
 */
public class Tea extends Beverage{
    @Override
    void brew() {
        System.out.println("制作茶叶");
    }

    @Override
    void addCondiments() {
        System.out.println("加入桂花");
    }
}

这就是模板方法的具体实现,但其实模板方法中通常有一个钩子函数,钩子函数可以让子类实现算法中的可选部分,或者改变算法中的某个流程,同样即使不重写钩子, 模板方法也能工作,钩子就是为模板方法提供了扩展点。下面为大家展示一个带钩子的模板方法:

  • 饮料类(带钩子)
/**
 * 饮料
 *
 */
public abstract class Beverage {
    /**
     * 准备饮料
     * 模板方法,定义算法骨架,因此设置为final子类不该修改
     */
    final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        //通过钩子来判断要不要加调料
        if (hook()){
            addCondiments();
        }
    }

    /**
     * 制作
     */
    abstract void brew();

    /**
     * 添加调味品
     */
    abstract void addCondiments();

    /**
     * 煮水
     */
    void boilWater(){
        System.out.println("将水烧开");
    }

    /**
     * 倒入杯子
     */
    void pourInCup(){
        System.out.println("将饮料倒入杯子");
    }

    /**
     * 钩子
     *
     *
     */
    boolean hook(){
        return true;
    }
}

通过这个示例我们可以看到子类可以通过自己的需要,重写钩子来改变算法流程。

三、模式总结

模板方法有很多好处,比如减少了冗余的代码、只需要某个步骤即可完成某个算法流程等。在学习模板方法时相信很多小伙伴会觉得,模板方法和策略模式是比较相似的,策略模式不也是根据某种算法族生成不同种算法吗?但其实仔细想想,从模式的含义来看:策略模式为了能够在运行时切换不同种算法,而模板方法却是为了定义一个算法骨架,由子类来实现某些步骤的细节。在实现方式上:策略模式通过组合来实现,而模板方法则是通过继承来实现。其实最终我明白了一个道理,在设计一个项目上,不同模式之间并不是并行的关系,而是可以串行的可以互相配合使用。

四、适用场景

  • 当你只希望客户端扩展某个特定算法步骤,而不是整个算法或其结构时,可使用模板方法模式

  • 当多个类的算法除一些细微不同之外几乎完全一样时, 你可使用该模式。但其后果就是,只要算法发生变化,你就可能需要修改所有的类。