八、模板方法模式(Template Method Pattern)《HeadFirst设计模式》读书笔记

  在抽象类中创建一个模板方法,这个方法可以调用在抽象类中定义好的其它方法,这些方法可以是抽象的,也可以是默认方法,甚至还可以是一个空的方法(叫做钩子),可以在子类中重写抽象方法或重写钩子方法,从而实现模板方法的某些具体步骤,这就是模板方法模式。模板方法模式可以实现代码的复用。

  让我们来看一个具体的例子:

  咖啡和柠檬茶的制作过程都要经过四个步骤:

    1.烧开水;

    2.冲咖啡/泡茶叶;

    3.倒入杯中;

    4.加糖和牛奶/加柠檬。

  可以看到,四个步骤中第1步和第3步其实是一样的,如果把上面的过程分别写进咖啡类和柠檬茶类中的话,那每个类中会有两个方法是重复的,如何实现方法的复用呢?

  经过了一些设计模式的学习,我们发现很多设计模式都是通过组合和继承来实现的,这里说说我自己的想法:

    1.如果通过组合的方式,可以像命令模式那样把不同的操作步骤封装成一个对象中的方法,然后在咖啡类和柠檬茶类中分别组合这些对象,这样每个步骤都可以被复用;

    2.如果通过继承的方式,可以抽象出一个父类,在父类中将相同的步骤1和3写成默认方法,将不同的步骤写成抽象方法,让咖啡和柠檬茶继承这个父类,并分别提供自己对抽象方法的重写。

  上面说的第二种方式其实就是模板方法模式的标准形式,来看一下具体代码实现:

public abstract class Father {
    //模板方法,不希望被改变(被子类覆盖),所以定义成final的
    public final void templateMethod(){
        boilWater();
        brew();
        pourIntoCup();
        addOther();
    }
    //步骤1
    public void boilWater(){
        System.out.println("烧开水");
    }
    //步骤2
    public abstract void brew();
    //步骤3
    public void pourIntoCup(){
        System.out.println("倒入杯中");
    }
    //步骤4
    public abstract void addOther();
}

public class Coffee extends Father {
    //在咖啡类中重写父类中的抽象方法
    @Override
    public void brew() {
        System.out.println("冲咖啡");
    }
    @Override
    public void addOther() {
        System.out.println("加糖和牛奶");
    }
}

public class LemonTea extends Father {
    //在柠檬茶类中重写父类中的抽象方法
    @Override
    public void brew() {
        System.out.println("泡茶");
    }
    @Override
    public void addOther() {
        System.out.println("加柠檬");
    }
}

//测试
public class test01 {
    public static void main(String[] args) {
        Coffee coffee = new Coffee();
        LemonTea lemonTea = new LemonTea();
        coffee.templateMethod();
        lemonTea.templateMethod();
    }
}

  测试结果:

   注意到如果不希望模板方法被覆盖,可以声明成final。模板方法定义好了整个方法的框架,有些具体的部分可以在子类中去实现。

  另外上面提到的钩子又是怎么一回事呢,我们改造一下上面的例子,在模板方法中加入一个hook()方法:

public abstract class Father {
    //模板方法,不希望被改变(被子类覆盖),所以定义成final的
    public final void templateMethod(){
        boilWater();
        brew();
        pourIntoCup();
        addOther();
        hook();
    }

    private void hook() {
        //什么都不做,由子类决定是否覆盖
    }

    //步骤1
    public void boilWater(){
        System.out.println("烧开水");
    }
    //步骤2
    public abstract void brew();
    //步骤3
    public void pourIntoCup(){
        System.out.println("倒入杯中");
    }
    //步骤4
    public abstract void addOther();
}

  可以看到这个hook()方法并不是抽象方法,也就是子类覆不覆盖都可以,这样可以更灵活的附加一些功能。另外还可以做一些条件判断来控制模板方法内一些方法的执行,比如:

public abstract class Father {
    //模板方法,不希望被改变(被子类覆盖),所以定义成final的
    public final void templateMethod(){
        boilWater();
        brew();
        pourIntoCup();
        //是否要加额外的配料
        if (wannaAdd()) {
            addOther();
        }
    }
    //默认返回true
    private boolean wannaAdd() {
        return true;
    }

    //步骤1
    public void boilWater(){
        System.out.println("烧开水");
    }
    //步骤2
    public abstract void brew();
    //步骤3
    public void pourIntoCup(){
        System.out.println("倒入杯中");
    }
    //步骤4
    public abstract void addOther();
}

  上面的案例默认返回true,也就是要加配料,子类可以覆盖这个方法返回false,还可以让用户输入是否要加配料来返回true/false,钩子为模板方法提供了更多的灵活性。

  另外上面的例子是模板方法的标准形式,那么不标准的呢,下面说一个实际的例子。在JDK1.8中的Arrays类中的静态方法sort(Object[] o),它可以对数组中的元素进行排序,在sort(Object[] o)方法中调用了一个legacyMergeSort()方法,这个方法内又调用了mergeSort()方法,这是一个模板方法,在这个方法中自己实现了一部分,另外抽象的方法是通过将传递进来的数组元素强转成Comparable接口调用的compareTo()方法,因此数组元素要实现Comparable接口并重写compareTo()方法。可见这个模板方法的实现和上面的标准形式稍微有些不同,因为数组的特殊性,JDK在Arrays工具类写了一些处理数组的方法,比如sort(),在内部的模板方法内,通过数组元素实现Comparable接口并重写compareTo()方法,实现将具体实现延迟到子类(数组元素)中,由这种表述方式也可以知道其实工厂方法也是一种特殊的模板方法,只不过它是用来创建对象的。

  总结:

    1.模板方法定义了算法的步骤,一些步骤可以在子类中实现,实现了代码的复用,还可以通过钩子实现更灵活的控制;

    2.如果不想让子类改变模板方法,可以将模板方法定义成final的

    3.模板方法也有一些衍生情况,有时并不一定是在模板方法所在类的子类中去实现方法,比如Arrays工具类,就在模板方法中调用了Comparable接口的抽象方法compareTo(),具体实现是在数组元素中重写的。

    4.工厂方法其实也可以看成是模板方法的一种特殊版本,父类中的创建对象的抽象方法就可以看成模板方法,在子类中去返回具体的对象。

    5.模板方法的特殊情况其实有点类似于通过组合来实现的,只不过传递进来参数不是接口而是具体实现然后再强转成接口,如果说是组合的话那就有点类似于策略模式,不同的是策略模式通常通过组合实现了算法的全部,而模板方法只是实现了模板中的一部分。

 

posted @ 2020-07-19 21:18  ADvancedCZ  阅读(118)  评论(0编辑  收藏  举报