设计模式之模板方法模式(二)

 

上一篇我们已经学会了模板方法模式,这次我们继续来深入学习下。毕竟学会只是第一步,还有更进一步的学习和深入等着我们呢。

我们先来看下,对模板方法模式的一个总结的类图:

640?wx_fmt=jpeg

让我们细看抽象类是如何被定义的,包含了它内含的模板方法和原语操作。

abstract class AbstractClass {// 这就是模板方法。它被声明为final,以免子类改变这个算法的顺序    final void templateMethod() {    // 模板方法定义了一连串的步骤,每个步骤由一个方法代表        primitiveOperation1();        primitiveOperation2();        concreteOperation();        hook();    }    abstract void primitiveOperation1();    abstract void primitiveOperation2();    final void concreteOperation() {        // 这里是实现    }    // 这是一个具体的方法,但他什么都不做。我们叫它为hook(钩子),马上就来揭晓它如何使用    void hook();}class AbstractClass {
// 这就是模板方法。它被声明为final,以免子类改变这个算法的顺序
    final void templateMethod() {
    // 模板方法定义了一连串的步骤,每个步骤由一个方法代表
        primitiveOperation1();
        primitiveOperation2();
        concreteOperation();
        hook();
    }

    abstract void primitiveOperation1();

    abstract void primitiveOperation2();

    final void concreteOperation() {
        // 这里是实现
    }

    // 这是一个具体的方法,但他什么都不做。我们叫它为hook(钩子),马上就来揭晓它如何使用
    void hook();
}

对模板方法进行挂钩

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

钩子有很多用途,我们先看其中一个:

public abstract class CaffeineBeverageWithHook {    final void prepareRecipe() {        boilWater();        brew();        pourInCup();        // 我们加上一个小小的条件语句,该条件是否成立,是由一个具体方法决定        if (customerWantsCondiments()) {            addCondiments();        }    }    abstract void brew();    abstract void addCondiments();    void boilWater() {        System.out.println("Boiling water");    }    void pourInCup() {        System.out.println("Pouring into cup");    }    // 这就是一个钩子,子类可以覆盖这个方法,但不一定需要使用    boolean customerWantsCondiments() {        return true;    }}abstract class CaffeineBeverageWithHook {

    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        // 我们加上一个小小的条件语句,该条件是否成立,是由一个具体方法决定
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    abstract void brew();

    abstract void addCondiments();

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

    void pourInCup() {
        System.out.println("Pouring into cup");
    }

    // 这就是一个钩子,子类可以覆盖这个方法,但不一定需要使用
    boolean customerWantsCondiments() {
        return true;
    }
}

为了使用钩子,我们在子类中覆盖它。在这里,钩子控制了咖啡因饮料是否执行某部分算法;比如,饮料中是否需要加进调料

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;    }}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 BeverageTestDrive {    public static void main(String[] args) {        Tea tea = new Tea();        Coffee coffee = new Coffee();        System.out.println("\nMaking tea...");        tea.prepareRecipe();        System.out.println("\nMaking coffee...");        coffee.prepareRecipe();        TeaWithHook teaHook = new TeaWithHook();        CoffeeWithHook coffeeHook = new CoffeeWithHook();        System.out.println("\nMaking tea...");        teaHook.prepareRecipe();        System.out.println("\nMaking coffee...");        coffeeHook.prepareRecipe();    }}class BeverageTestDrive {
    public static void main(String[] args) {

        Tea tea = new Tea();
        Coffee coffee = new Coffee();

        System.out.println("\nMaking tea...");
        tea.prepareRecipe();

        System.out.println("\nMaking coffee...");
        coffee.prepareRecipe();


        TeaWithHook teaHook = new TeaWithHook();
        CoffeeWithHook coffeeHook = new CoffeeWithHook();

        System.out.println("\nMaking tea...");
        teaHook.prepareRecipe();

        System.out.println("\nMaking coffee...");
        coffeeHook.prepareRecipe();
    }
}

执行结果如下:茶要加调料就给你加上了;咖啡不需要加调料,就没给你加

Making tea...Boiling waterSteeping the teaPouring into cupWould you like lemon with your tea (y/n)? yAdding LemonMaking coffee...Boiling waterDripping Coffee through filterPouring into cupWould you like milk and sugar with your coffee (y/n)? nBoiling water
Steeping the tea
Pouring into cup
Would you like lemon with your tea (y/n)? y
Adding Lemon

Making coffee...
Boiling water
Dripping Coffee through filter
Pouring into cup
Would you like milk and sugar with your coffee (y/n)? n

那么,我们使用钩子的真正目的是什么呢?

钩子有几种用法。如我们之前所说的,钩子可以让子类实现算法中可选的部分,或者在钩子对于子类的实现并不重要的时候,子类可以对此钩子置之不理。钩子的另一个用法,是让子类能够 有机会对模板方法中某些即将发生的(或刚刚发生的)步骤做出反应。比方说,名为justReOrderedList()的钩子方法允许子类在内部列表重新组织后执行某些动作(例如在屏幕上重新显示数据)。正如你刚刚看到的,钩子也可以让子类有能力为其抽象类做一些决定。

好莱坞原则

好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。

好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖低层组件时,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。

在好莱坞原则下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用我们,我们会调用你”。

好莱坞原则和模板方法之间的连接其实还算明显:当我们设计模板方法时,我们告诉子类“不要调用我们,我们会调用你”。怎样才能办到呢?让我们再看一次咖啡因饮料的设计:

640?wx_fmt=jpeg

我们之前还知道一个原则叫依赖倒置原则,好莱坞原则也是有点这个味道的对吧。他们之间的关系是如何的呢?

依赖倒置原则教我们尽量避免使用具体类,而多实用抽象。而好莱坞原则是用在创建框架或组件上的一种技巧,好让低层组件能够被挂钩进计算中,而且又不会让高层组件依赖低层组件。两者的目标都是在于解耦,但是以来倒置原则更加注重如何在设计中避免依赖。

好莱坞原则教我们一个技巧,创建一个有弹性的设计,允许低层结构能够互相操作,而又防止其他类太过于依赖它们。

这样我们就把开篇说的隐藏的原则给介绍完了,也更进一步的知道了模板方法模式钩子的用法,让我们在实战中能有一个更好的选择。这个设计模式,你get到了吗?

小编本来想在这完结的,但是看了下书,发现后面还有一个更贴近实际的,意想不到的模板方法,而且是我们平时会使用到的,我们下篇来聊聊。

爱生活,爱学习,爱感悟,爱挨踢

640?wx_fmt=jpeg

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小跃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值