设计模式之模板方法模式

小明拿到了一个半成品

这天,主管发了一个项目给小明,说这是之前咖啡厅的一个小需求,他们的生意越来越好,开始买其他的饮品了,比如茶。咖啡店想把泡咖啡和泡茶的过程规范一下,所以需要小明他们公司迭代一个程序的版本。

但是小明打开项目后,发现项目里面只有两个类,其中一个是抽象类,而主管的说,你的任务就是实现这个抽象类,完成这个项目。

饮料抽象类:

public abstract clsaa Beverage{

    final void made(){
        boilWater();
        addBeverage();
        waterInCup();
        addCondiments();
    }

    abstract void addBeverage();

    abstract void addCondiments();

    void boilWater(){
        // 煮开水
    }

    void waterInCup(){
        // 把水放进水杯
    }
}

很快小明实现了咖啡和茶:

public clsaa Coffee extends Beverage{

    void addBeverage(){
        // 加咖啡
    }

    void addCondiments(){
        // 加其他调料
    }
}
public clsaa Tea extends Beverage{

    void addBeverage(){
        // 加茶叶
    }

    void addCondiments(){
        // 加其他调料
    }
}

主管说好,我们现在可以开始煮咖啡、泡茶了:

public class Customer {

    public static void main(String[] args){

        // 煮咖啡
        Beverage coffee = new Beverage();
        coffee.made();

        // 泡茶
        Beverage tea = new Tea();
        tea.made();

    }
}

小明发现这种方式真不错呢,于是请教了主管,这是什么用法。主管说,这叫模板方法模式,我们可以看到在抽象类中,我们定义了一些抽象方法,这些方法由子类去实现,还定义了一些默认实现的方法(boilWater()、waterInCup()),另外还有一个被final修饰的made()方法,我们把这个方法就称为模板方法(不会被子类覆盖),它调用了一些抽象方法和已经实现的方法去实现,抽象方法中的实现不是在抽象类中,而是在子类中,这就是我们的模板方法模式。

定义

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

模板方法模式

优点:

  1. 模板方法模式在一个类中形式化地定义算法,而由它的子类实现细节的处理
  2. 模板方法是一种代码复用的基本技术。它们在类库中尤为重要,它们提取了类库中的公共行为
  3. 模板方法模式导致一种反向的控制结构,这种结构有时被称为“好莱坞法则” ,即“别找我们,我们找你”通过一个父类调用其子类的操作(而不是相反的子类调用父类),通过对子类的扩展增加新的行为,符合“开闭原则”

缺点:

  1. 每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合“单一职责原则”,使得类的内聚性得以提高

再加一个方法

public abstract clsaa Beverage{

    final void made(){
        boilWater();
        addBeverage();
        waterInCup();
        addCondiments();
        addIce();
    }

    abstract void addBeverage();

    abstract void addCondiments();

    void boilWater(){
        // 煮开水
    }

    void waterInCup(){
        // 把水放进水杯
    }

    void addIce(){
        // 不实现
    }
}

在抽象类里面,我们又加了一个方法,偶尔顾客可能需要在咖啡或者茶里面加冰块,于是增加了addIce()方法。他和之前我们的三种方法都不同,他是一个没有实现的方法——钩子方法。那钩子方法的作用是什么呢,模板方法定义了整个算法,子类实现算法中的一部分,但是仅仅是这样,子类的作用就只是实现,而为了让子类也能做一些决定,于是加入了钩子方法,也就是钩子方法是为了让子类也可以做一些决定,如果它不想做决定,那么不去覆盖钩子方法就可以了。仿佛有点绕了,我们来理一理:抽象对象依赖了实现它的子类,子类的整个功能又依赖了它的抽象父类,父类由依赖了钩子方法,而钩子方法又由子类来决定,这不违背了我们的依赖倒置原则吗?其实它是遵循了我们的另外一个原则好莱坞原则:别调用我们,我们会调用你。子类不需要调用父类,而通过父类来调用子类,子类可以实现父类的可变部份,却继承父类的逻辑,不能改变业务逻辑。

现实中的模板方法使用

Java中大家一定使用过Arrays.sort(Object[] array)方法,其实它就是模板方法的一个应用,不过它又和我们上面的实现有所不同,看看排序具体的方法:

private static void binarySort(Object[] a, int lo, int hi, int start) {
    if (DEBUG) assert lo <= start && start <= hi;
    if (start == lo)
        start++;
    for ( ; start < hi; start++) {
        @SuppressWarnings("unchecked")
        Comparable<Object> pivot = (Comparable) a[start];

        // Set left (and right) to the index where a[start] (pivot) belongs
        int left = lo;
        int right = start;
        if (DEBUG) assert left <= right;
        /*
         * Invariants:
         *   pivot >= all in [lo, left).
         *   pivot <  all in [right, start).
         */
        while (left < right) {
            int mid = (left + right) >>> 1;
            if (pivot.compareTo(a[mid]) < 0)
                right = mid;
            else
                left = mid + 1;
        }
        if (DEBUG) assert left == right;

        /*
         * The invariants still hold: pivot >= all in [lo, left) and
         * pivot < all in [left, start), so pivot belongs at left.  Note
         * that if there are elements equal to pivot, left points to the
         * first slot after them -- that's why this sort is stable.
         * Slide elements over to make room to make room for pivot.
         */
        int n = start - left;  // The number of elements to move
        // Switch is just an optimization for arraycopy in default case
        switch(n) {
            case 2:  a[left + 2] = a[left + 1];
            case 1:  a[left + 1] = a[left];
                     break;
            default: System.arraycopy(a, left, a, left + 1, n);
        }
        a[left] = pivot;
    }
}

注意两个地方:Comparable<Object> pivot = (Comparable) a[start]if (pivot.compareTo(a[mid]) < 0),首先它将我们做排序的类类型转换成了Comparable接口,而这个接口有一个方法需要实现就是:

int compareTo(T another);

大家应该比较清楚了,如果我们需要自定义我们对象的排序规则,就需要实现Comparable,然后实现compareTo方法,根据自己的需求返回不同的值。是不是和我们之前的方式完全不同,但是它的确是模板方法的具体使用。所以,模板方法的使用可能形式多种多样,只要是遵守模板方法的大义,就像这个例子,静态方法sort通过和Comparable的组合实现了排序,但是中心思想也是被比较的对象来实现了排序中的大小比较的规则。等一下!这个方式用策略模式来称呼它不是更合适么?通过组合,不关心实现!所以不得不说一说他们之间的区别,最大的区别:策略模式实现整个算法,而模板方式的子类只是实现了整个算法中的一部分。像上面的排序,整个排序方法是sort,而compareTo方法只是辅助sort方法而已。

还有一个和它类似的模式,他们之间才是有关联的,那就是工厂方法,工厂方法模式是一种特殊的模板方法,特殊就特殊在工厂方法模式的方法是为了专门实例化需要的类。

坚持原创分享,您的支持将鼓励我不断前行!