继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Head First设计模式读书总结——模板方法模式

尕小刘
关注TA
已关注
手记 12
粉丝 26
获赞 228

本章我们将要深入封装算法块,好让子类可以在任何时候都将自己挂接进运算里。
有些人不能没有咖啡;有些人则离不开茶,两者共同成分是什么?我们来看看茶和咖啡的冲泡方式:
星巴兹咖啡冲泡法:
1:把水煮沸
2:用沸水冲泡咖啡
3:把咖啡倒进杯子
4:加糖和牛奶
星巴兹茶冲泡法
1:把水煮沸
2:用沸水浸泡茶叶
3:把茶倒进杯子
4:加柠檬
快速搞定几个咖啡和茶的类

class Coffe{
    void prepareRecipe(){
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugarAndMilk();
    }
    puВLic void boilWater(){
        System.out.println("Bolling water");
    }
    puВLic void brewCoffeeGrinds(){
        System.out.println("Dripping Coffee through filter");
    }
    puВLic void pourInCup(){
        System.out.println("Pouring into cup");
    }
    puВLic void addSugarAndMilk(){
        System.out.println("Adding Sugar and Milk");
    }
}

接下来是茶

class Tea{
    void prepareRecipe(){
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }
    puВLic void boilWater(){
        System.out.println("Bolling water");
    }
    puВLic void steepTeaBag(){
        System.out.println("Steeping the tea");
    }
    puВLic void pourInCup(){
        System.out.println("Pouring into cup");
    }
    puВLic void addLemon(){
        System.out.println("Adding Lemon");
    }
}

我们发现了重复的代码,似乎我们应该将共同的部分抽取出来,放进一个基类中。可能看起来像这样:
图片描述
更近异步的设计
咖啡和茶还有什么其他共同点呢?
1:把水煮沸
2:用沸水跑咖啡或茶
3:把饮料倒进杯子
4:在饮料内加入适当的调料
1和3已经被我们抽圌出来,放到基类中了。
2和4这两个没有抽圌出来,但是他们是一样的,只是应用在不同的饮料上。
浸泡和冲泡差异其实不大。所以我们给它一个新的方法名称,brew(),然后不管泡茶或冲泡咖啡我们都用这个名称。类似的加糖和牛奶也和加柠檬很相似,都是在饮料中加入调料。让我们也给它一个新的方法名称,addCondiments()。这样一来新的prepareRecipe()方法就出来了。
我们先从CaffeineBeverage(咖圌啡圌因饮料)超类开始:

abstract class CaffeineBeverage{
   final  void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
    puВLic void boilWater(){
        System.out.println("Bolling water");
    }
    abstract void brew();
    puВLic void pourInCup(){
        System.out.println("Pouring into cup");
    }
    abstract void addCondiments();
}

最后我们需要处理咖啡和茶类了,这两个类现在都是依赖超类(咖圌啡圌因饮料)来处理冲泡法。所以只需要自行处理冲泡和添加调料部分:

class Tea_ extends CaffeineBeverage {

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

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

class  Coffe_ extends CaffeineBeverage{

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

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

认识模板方法
基本上,我们刚刚实现的就是模板方法模式。在我们的咖啡饮料类(CaffeineBeverage)的结构,它包含了实际的“模板方法”。
prepareRecipe()就是我们的模板方法。它用作一个算法的模板,在这个例子中,算法是用来制作咖啡饮料的。
模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
定义模板方法模式
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板方法模式类图

//这就是我们的抽象类,它被声明为抽象,用来作为基类,其子类必须实现其操作
abstract class AbstractClass{
    //这就是模板方法,它被声明为final,以免子类改变这个算法的顺序
    final void templateMethod(){
        primitiveOperation1();
        primitiveOperation2();
        concreteOperation();
    }
    //在这个范例中有两个原语操作,具体子类必须实现它们
    abstract void primitiveOperation1();
    abstract void primitiveOperation2();
    void concreteOperation(){
        //这个抽象类有一个具体的操作,关于这个方法,稍后会再详述
    }
}
abstract class AbstractClass_{
    //我们加进一个新方法调用,改变了templateMethod
    final void templateMethod(){
        primitiveOperation1();
        primitiveOperation2();
        concreteOperation();
        hook();
    }
    abstract void primitiveOperation1();
    abstract void primitiveOperation2();
    final void concreteOperation(){
        //这里是实现
        //这个具体的方法被定义在抽象类中。将它声明为final,这样一来子类就无法覆盖它
        //它可以被模板方法直接使用或被子类使用
    }
    /*
    这是一个具体的方法,但是它什么事情都不做
    我们也可以有“默认不做事的方法”,我们称这种方法为”hook“(钩子)
    子类可以视情况决定要不要覆盖它们。后面会知道实际用途
     */
    void hook(){}

}

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

abstract class CaffeineBeverageWithHook{
    final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        /*
        我们加上了一个小小的条件语句,而该条件是否成立,是由一个具体方法
        customerWantsCondiments()决定的,如果顾客想要调料,只有这时候
        我们才调用addCondiments()。
         */
        if(customerWantsCondiments()){
            addCondiments();
        }
    }
    puВLic void boilWater(){
        System.out.println("Bolling water");
    }
    abstract void brew();
    puВLic void pourInCup(){
        System.out.println("Pouring into cup");
    }
    abstract void addCondiments();
    /*
    我们在这里定义了一个方法,(通常)是空的缺省实现,
    这个方法只会返回true,不做别的事。
    这就是一个钩子,子类可以负载这个方法,但不见得一定要这么做。
     */
    boolean customerWantsCondiments(){
        return true;
    }
}

好莱坞原则:别调用我们,我们会调用你。
好莱坞原则可以给我们一种防止“依赖不好”的方法。当高层组件依赖底层组件,而底层组件又依赖高层组件,而刚曾组件又依赖边侧组件,而边侧组件又依赖底层组件时,依赖不好就发生了,这种情况,没人轻易搞懂系统是如何设计的。
在好莱坞原则之下,我们允许底层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些底层组件,换句话说,刚层组件对待底层组件的方式是“别调用我们,我们会调用你”。
荒野中的模板方法
模板方法模式是一个很常见的模式,到处都是,尽管如此,你必须用用一双锐利的眼睛,因为末班方法有旭东实现,而它们看起来并不一定和书上所讲的设计一直。
这个模式很常见是因为对创建框架来说,很棒,由框架控制如何做事情,而由你指定框架算法中每个步骤的细节。

打开App,阅读手记
2人推荐
发表评论
随时随地看视频慕课网APP