1. 定义

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

2. 作用

基于继承的代码复用技术,在模板方法模式中,可以将相同的代码放在父类中,而将不同的实现放在子类中。抽象类将部分逻辑以具体方法的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。

3. 结构

  • 抽象类

    定义模板方法,也就是逻辑的骨架,细化的操作定义成抽象方法由子类实现。

  • 具体类

    实现抽象类的抽象方法,实现本身的具体操作。

4. 实现

举个例子:泡茶和冲咖啡。都要经过烧水、放料和冲泡的过程,其中烧水和冲泡是一样的操作,而放入的原料却不同。所以,定义一个冲泡热饮的基本方法:烧水--放料--冲泡,放料定义为抽象方法,由茶和咖啡分别实现。

另外,我们可以决定是否加糖,默认是加糖(甜是大家都爱的嘛),在这里咖啡需要加糖,而茶却不需要。所以,茶类可以覆盖抽象类的默认实现,那么在泡茶的时候,就不会加糖啦。这个方法称作钩子方法,具体类可以通过它来控制抽象类的行为。

类图
public abstract class CoffeineBeverage {

    /**
     * 模板方法,不允许子类覆盖
     */
    public final void prepareRecipe() {
        boilWater();
        addCondiment();
        brew();
        if (isAddSugar()) {
            System.out.println("加糖");
        }
    }

    private void boilWater() {
        System.out.println("烧水");
    }

    private void brew() {
        System.out.println("冲泡");
    }

    /**
     * 是否加糖,钩子方法
     *
     * @return
     */
    protected boolean isAddSugar() {
        return true;
    }

    /**
     * 加料
     */
    protected abstract void addCondiment();
}
public class Coffee extends CoffeineBeverage {

    @Override
    protected void addCondiment() {
        System.out.println("加入咖啡");
    }
}

public class Tea extends CoffeineBeverage {

    @Override
    protected void addCondiment() {
        System.out.println("加入茶叶");
    }

    @Override
    protected boolean isAddSugar() {
        return false;
    }
}
    public static void main(String[] args) {
        // 冲咖啡
        CoffeineBeverage coffee = new Coffee();
        coffee.prepareRecipe();

        System.out.println("-----------------");
        // 泡茶
        CoffeineBeverage tea = new Tea();
        tea.prepareRecipe();
    }

5. 优缺点

1. 优点:

封装不变部分,扩展可变部分,提高了代码的复用性;实现了反向的控制结构,父类调用其子类的操作,子类的扩展增加新的行为,符合「开闭原则」。

2. 缺点:

每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。

3. 使用场景:

  • 一次性实现算法的不变部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
  • 控制子类扩展。模板方法只在特定点调用“hook”操作 ,这样就只允许在这些点进行扩展。

参考文章:

模板方法模式