设计模式:模板方法

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

  场景,对于咖啡来说,我们需要把水煮沸,然后用沸水冲泡咖啡,再把咖啡倒入杯子中,最后加入糖和牛奶,对于茶而言,也是先把水煮沸,用沸水去浸泡茶叶,在把茶倒入杯子中,最后加上柠檬。我们可以看这两种方法其实都采用了相同算法,1:把水煮沸 2:用沸水泡咖啡或茶 3:把咖啡因饮料倒入杯子中 4:在杯子中加入相应调料。现在我们用代码去实现看看

  首先先创建一个咖啡因饮料的抽象类

//定义一个咖啡因饮料的抽象类
public abstract class CaffeineBeverage {
    
    //浸泡茶和冲泡咖啡其实差不多,我们这就用brew方法来表示
    //声明为final是因为我们不希望子类覆盖这算法型的方法。我们将步骤2和4泛化成方法brew和addCondiments。
    //这里prepareRecipe是我们的模板方法(1:这是一个方法,2:它用作一个方法的模板,这里是制作咖啡因饮料的),在这个模板中,算法内每一个步骤都被一个方法代表了,其中某些方法在这个类中处理,某些需要子类实现
    final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
    
    abstract void brew();
    abstract void addCondiments();
    
    void boilWater(){
        System.out.println("1:把水煮沸开来");
    }
    
    void pourInCup(){
        System.out.println("3:到入杯子中");
    }
}

  然后创建茶和咖啡类去实现这个抽象类

//茶继承咖啡因饮料这抽象类,并实现自己的冲泡和添加调料的方法
public class Tea extends CaffeineBeverage{

    @Override
    void brew() {
        System.out.println("2:将茶浸泡在沸水中");
    }

    @Override
    void addCondiments() {
        System.out.println("4:加入柠檬");
    }
}

//咖啡继承咖啡因饮料这抽象类,并实现自己的冲泡和添加调料的方法
public class Coffee extends CaffeineBeverage{

    @Override
    void brew() {
        System.out.println("2:用沸水冲泡咖啡");
    }

    @Override
    void addCondiments() {
        System.out.println("4:加入糖和牛奶");
    }
}

  最后我们进行测试

public class Test {
    public static void main(String[] args) {
        //测试茶的制作过程
        Tea tea = new Tea();
        tea.prepareRecipe();
        
        System.out.println("----");
        
        //测试咖啡的制作过程
        Coffee coffee = new Coffee();
        coffee.prepareRecipe();
    }
}

  运行结果如下:

      

  好了,这就是一个简单的模板方法模式了,我们总结下怎么定义抽象类的,首先抽象类是作为基类的,子类必须实现其操作,然后模板方法被声明了final,以免子类改变这个算法的内容,在模板方法中定义了一连串的步骤,每个步骤有一个方法代表。

  最后,在讲下在模板方法中比较常用的“钩子”,什么是模板方法中的“钩子”?其实就是被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在可以让子类有能力对算法的不同点进行挂钩。下面我们将上面场景加入钩子,用钩子来询问客户是否需要加入调料。

  抽象类代码如下:

public abstract class CaffeineBeverage {
    
    final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        if(customerWantsCondiments()){
            addCondiments();
        }
    }
    
    abstract void brew();
    abstract void addCondiments();
    
    void boilWater(){
        System.out.println("1:把水煮沸开来");
    }
    
    void pourInCup(){
        System.out.println("3:到入杯子中");
    }
    
    //这就是钩子,默认是空的实现,这里返回true不做任何事情,子类可以视情况决定要不要覆盖它们
    boolean customerWantsCondiments(){
        return true;
    }
}

  接着定义的两个实现类如下

//茶类,覆盖了使用了钩子方法
public class TeaWithHook extends CaffeineBeverage {

    @Override
    void brew() {
        System.out.println("2:将茶浸泡在沸水中");
    }

    @Override
    void addCondiments() {
        System.out.println("4:加入柠檬");
    }
    
    public boolean customerWantsCondiments(){
        String answer = UserInput();
        if(answer.toLowerCase().startsWith("y")){
            return true;
        }
        else{
            System.out.println("不加入任何东西");
            return false;
        }
    }
    
    public String UserInput(){
        String answer = null;
        
        System.out.println("你想在茶中加入柠檬吗?(y/n)");
        
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try{
            answer = reader.readLine();
        }catch(IOException e){
            System.err.println("输入错误!");
        }
        return answer;
    }
}

//咖啡类,覆盖了使用了钩子方法
public class CoffeeWithHook extends CaffeineBeverage {

    @Override
    void brew() {
        System.out.println("2:用沸水冲泡咖啡");
    }

    @Override
    void addCondiments() {
        System.out.println("4:加入糖和牛奶");
    }
    
    public boolean customerWantsCondiments(){
        String answer = UserInput();
        if(answer.toLowerCase().startsWith("y")){
            return true;
        }
        else{
            System.out.println("不加入任何东西");
            return false;
        }
    }
    
    public String UserInput(){
        String answer = null;
        
        System.out.println("你想在咖啡中加入糖和牛奶吗?(y/n)");
        
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try{
            answer = reader.readLine();
        }catch(IOException e){
            System.err.println("输入错误!");
        }
        return answer;
    }
}

  最后我们进行钩子的测试类编写

public class Test2 {
    public static void main(String[] args) {
        TeaWithHook teaWithHook = new TeaWithHook();
        teaWithHook.prepareRecipe();
        
        System.out.println("-----");
        CoffeeWithHook coffeeWithHook = new CoffeeWithHook();
        coffeeWithHook.prepareRecipe();
    }
    
}

  运行结果如下

      

  好了,这就是模板方法中简单的钩子使用了。

  让我们区别下模板方法、策略、工厂方法模式的区别吧,模板方法是子类决定如何实现算法中的某些步骤,策略模式是封装可互换的行为,然后使用委托来决定采用哪一个行为,工厂方法是由子类决定实例化哪个具体类。

  使用原则:好莱坞原则,别调用我们,我们会调用你。在此场景中,高层组件CaffeineBeverage控制冲泡的算法,只有在需要子类实现某个方法时才调用子类,如果Tea和Coffee没有先被调用,绝对不会直接调用抽象类的。

  

  下一节:迭代器模式

作者:哀&RT
出处:博客园哀&RT的技术博客--http://www.cnblogs.com/Tony-Anne/
您的支持是对博主最大的鼓励,感谢您的认真阅读。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
原文地址:https://www.cnblogs.com/Tony-Anne/p/6516475.html