八、模板方法模式

  • 定义

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

  • UML类图


    说明:
    1、AbstractClass抽象中包含了模板方法,primitiveOpration1() 和primitiveOpration2() 是这个模板方法所用到的操作的抽象版本。
    2、模板方法在实现算法的过程中,用到了两个原语操作。模板方法本身和这两个操作的具体实现之间被解耦了。
    3、ConcreteClass 可以有多个具体类,每一个都实现了模板方法所需要的全部操作。ConcreteClass中的primitiveOpration1()和primitiveOpration2(),当模板方法需要这两个抽象方法时,会调用他们。

简单实现

AbstractMethod 抽象的模板方法定义

/**
 *  模板方法模式在一个方法中定义了一个算法骨架,而将一些步骤延伸到子类中。
 *  模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤 
 */
public abstract class CaffeineBeverage {

    public final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    //该方法在模板的模式中被称作原语操作  具体子类必须实现它们
    protected abstract void addCondiments();

    protected abstract void brew();

    private final void pourInCup() {
        System.out.println("pour water in cup...");
    }

    private final void boilWater() {
        System.out.println("boil water...");
    }
}

ConcreteClass 抽象模板方法子类的实现

public class Tea extends CaffeineBeverage {

    @Override
    protected void addCondiments() {
        System.out.println("Adding Lemon...");
    }

    @Override
    protected void brew() {
        System.out.println("Adding Tea in Cup...");
    }
}
public class Coffee extends CaffeineBeverage {

    @Override
    protected void addCondiments() {
        System.out.println("Adding Sugar and Milk...");
    }

    @Override
    protected void brew() {
        System.out.println("Dripping Coffee through filter...");        
    }
}

测试类

public class BaristaTest {
    public static void main(String[] args) {
        Tea tea = new Tea();
        Coffee coffee = new Coffee();
        System.out.println("Making tea...");
        tea.prepareRecipe();
        System.out.println("Making coffee...");
        coffee.prepareRecipe();
    }
}

对模板方法进行挂钩操作

CaffeineBeverageWithHook 带钩子的抽象模板方法定义

public abstract class CaffeineBeverageWithHook {

    public final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        //条件判断语句,该条件是否成立,是有一个customerWantsCondiments()钩子方法决定的。
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    abstract void brew();

    abstract void addCondiments();

    private final void boilWater() {
        System.out.println("Boiling water");
    }

    private final void pourInCup() {
        System.out.println("Pouring into cup");
    }
    //这是一个钩子,子类可以覆盖这个方法,但是不见得一定这么做
    boolean customerWantsCondiments() {
        return true;
    }
}

使用钩子 CoffeeWithHook 和 TeaWithHook

public class CoffeeWithHook extends CaffeineBeverageWithHook {

    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    public void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }
     //钩子的使用
    public boolean customerWantsCondiments() {
        String answer = getUserInput();
        if (answer.toLowerCase().startsWith("y")) {
            return true;
        } else {
            return false;
        }
    }

    private String getUserInput() {
        String answer = null;
        System.out.print("Would you like milk and sugar with your coffee (y/n)? ");
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch (IOException ioe) {
            System.err.println("IO error trying to read your answer");
        }
        if (answer == null) {
            return "no";
        }
        return answer;
    }
}
public class TeaWithHook extends CaffeineBeverageWithHook {

    public void brew() {
        System.out.println("Steeping the tea");
    }

    public void addCondiments() {
        System.out.println("Adding Lemon");
    }
     //钩子的使用
    public boolean customerWantsCondiments() {
        String answer = getUserInput();
        if (answer.toLowerCase().startsWith("y")) {
            return true;
        } else {
            return false;
        }
    }

    private String getUserInput() {
        String answer = null;
        System.out.print("Would you like milk and sugar with your coffee (y/n)? ");
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch (IOException ioe) {
            System.err.println("IO error trying to read your answer");
        }
        if (answer == null) {
            return "no";
        }
        return answer;
    }
}

测试代码

public class BeverageTestDrive {
    public static void main(String[] args) {
        TeaWithHook teaHook = new TeaWithHook();
        CoffeeWithHook coffeeHook = new CoffeeWithHook();
        System.out.println("\nMaking tea...");
        teaHook.prepareRecipe();
        System.out.println("\nMaking coffee...");
        coffeeHook.prepareRecipe();
    }
}

说明:钩子是一种被声明在抽象类中的方法,但是只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。需不需要钩子,由子类自行决定。

  • 模板方法在Java排序中的实现

    Duck类的排序

public class Duck implements Comparable {
    String name;
    int weight;

    public Duck(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

    public String toString() {
        return name + " weighs " + weight;
    }

    public int compareTo(Object object) {

        Duck otherDuck = (Duck)object;

        if (this.weight < otherDuck.weight) {
            return -1;
        } else if (this.weight == otherDuck.weight) {
            return 0;
        } else { // this.weight > otherDuck.weight
            return 1;
        }
    }
}

测试方法

public class DuckSortTestDrive {
    public static void main(String[] args) {
        Duck[] ducks = { 
                new Duck("Daffy", 8), 
                new Duck("Dewey", 2), 
                new Duck("Howard", 7), 
                new Duck("Louie", 2),
                new Duck("Donald", 10), 
                new Duck("Huey", 2) 
                };
        System.out.println("Before sorting:");
        display(ducks);
        Arrays.sort(ducks);
        System.out.println("\nAfter sorting:");
        display(ducks);
    }

    public static void display(Duck[] ducks) {
        for (int i = 0; i < ducks.length; i++) {
            System.out.println(ducks[i]);
        }
    }
}

说明:
1、数组的排序模板算法已经提供了算法,但是要让这些模板方法知道如何比较duck,需要做的就是实现compareTo() 方法。
2、Array的设计者希望这个方法能够用于所有的数组,所以把sort()变成静态的方法,这样一来,任何数组都可以使用这个方法。
3、因为sort()并不是真正定义在超类中,所以sort()方法需要知道已经实现了这个compareTo()方法,否则无法进行排序。
4、compareTo()方法返回是一个int类型的变量,返回值为 1、0、-1 三种。
5、实现Comparable接口,并重写compareTo()方法。这种模板方法的实现,侧重在于提供一个算法,并让子类实现某些步骤,而数组的排序算法很明显正是如此。
6、在Duck类的排序算法中,由于无法继承Java数组,而sort()方法希望能够用于所有的数组(每个数组都是不同的类)。所以Arrays提供了一个静态方法,而由被排序对象内的每个元素自行提供比较大小的算法步骤。所以,这并不是标准的模板方法的实现,但是符合模板方法的模式精神。(由于不需要继承数组就可以实现排序算法,使得排序变得更有弹性、更有用)
7、在Java API的实现中,java.io的InputStream类有一个read()方法,是由子类实现的,而这个方法又会被 read(byte b[], int off, int len) 模板方法使用。

模板方法模式与策略模式的比较

1、数组对象的排序,这部分和策略模式相似,都是使用对象的组合。但是在策略模式中,所组合的类实现了整个算法,数组所实现的排序算法并不完整,他需要一个类填补compareTo()方法的实现。因此更像模板方法。
2、策略模式并不是使用继承来进行算法的实现,而是通过对象组合的方式,让客户可以选择算法的实现。而模板方法模式是定义一个算法大纲,而由子类定义其中某些步骤的内容。这样一来在算法的个别步骤可以有不同的细节实现,但算法的结构依然维持不变。
3、模板方法模式使用的重复代码,被放入到超类中,好让所有子类共享。而策略模式使用的是委托模型,使用对象组合,使得策略模式更加有弹性。利用策略模式,客户可以在运行时改变自己的算法,而客户所需要的,只是改用不同的策略对象罢了。
4、模板方法模式是经常被使用的模式,因为在超类中提供了一个基础的方法,达到代码的复用,并允许子类指定行为。这在创建框架的时候是非常有用的。
5、模板方法模式由于必须依赖超类中的方法的实现,算作算法的一部分。策略模式不需要依赖,整个算法自己去实现。所以依赖程度相对比较低。

要点

1、“模板方法”定义了算法的步骤,把这些步骤的实现延迟到子类。
2、模板方法为我们提供了一种代码复用的技巧。
3、模板方法的抽象类可以定义具体的方法、抽象方法和钩子。
4、抽象方法由子类实现。
5、钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它。
6、为了防止子类改变模板方法中的算法,可以将模板方法声明为final
7、将决策权放在高层模块中,以便决定如何及何时调用低层模块。
8、在真实实现的模板方法中,模板方法由很多的变体,不要指望一眼辨认出来。
9、策略模式和模板方法模式都封装算法,一个用组合,一个用继承。
10、工厂方法是模板方法的一种特殊版本。

posted @ 2017-02-19 21:49  春秋战国灞桥游  阅读(186)  评论(0编辑  收藏  举报