您的位置:首页 > 其它

【设计模式】学习笔记11:模板方法(Template Method)

2013-08-14 00:43 691 查看
本文出自 http://blog.csdn.net/shuangde800



—— 工厂方法是模板方法的一种特殊版本。

——如果你只想掌握一种设计模式,那么它就是模板方法!

走进模板方法

我们知道泡咖啡和泡茶的步骤是很类似的:

咖啡泡法

1. 把水煮沸

2. 用沸水冲泡咖啡

3.把咖啡倒进杯子

4.加糖和咖啡

泡茶方法

1.把水煮沸

2.用沸水侵泡茶叶

3.把茶倒进杯子

4.加柠檬

如何设计这两种饮料的类?

一个简单的方法是直接分别针对茶和咖啡设计一个类

咖啡Coffe类里面实现方法:
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();

茶Tea类里面实现方法:
boilWater();
steepTeaBad();
pourInCup();
addLemon();


但是这样的话,将会有重复代码,不符合设计原则。

1. Coffee和Tea将主导一切,它们控制了算法

2. Coffee和Tea之间存在着重复的代码

3. 对于算法所做的代码改变,需要打开子类修改许多地方

4. 由于类的组织方式不具有弹性,所以如果新加入种类的咖啡因因类需要做很多工作

5. 算法的只是和它的实现会分散在许多类中

我们可以发现方法1和3是一样的,而2,4步虽然稍有区别,但是差异也不大。因为侵泡和冲泡差别不大,所以我们大可以给它一个新的方法名称:brew(),然后不管是茶还是咖啡都用这个方法。类似地,加糖和牛奶以及加柠檬也是类似的:都是加入调料。所以变成新方法:addCondiments()

这样,我们可以给茶和咖啡定义一个共同的抽象类,然后再分别继承实现茶和咖啡的类



抽象类CaffeineBaverage(咖啡因饮料)中, prepareRecipe()方法是把泡茶(咖啡)的动作组合起来

boliWater(泡水)和pourlnCup(倒杯)方法因为是相同的,所以定义在超类中,并且可以直接实现。

而brew(冲泡)和addCondiments(加调料)因为不同东西方法不太一样,所以应定义为抽象方法,在具体子类中实现。

定义抽象类的代码:

// 都是咖啡因饮料,所以把抽象类命名为CaffeineBeverage
public abstract class CaffeineBeverage {
  
    // 用这个方法来处理泡茶和咖啡的步骤
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
 
    // 因为咖啡和茶的下面两种方法有些不同,
    // 所以定义成抽象的,具体做法留给子类定义
    abstract void brew();
  
    abstract void addCondiments();
 
    // 下面两种方法咖啡和茶是一样的,所以直接实现
    void boilWater() {
        System.out.println("Boiling water");
    }
  
    void pourInCup() {
        System.out.println("Pouring into cup");
    }
}


然后我们就可以分别实现子类咖啡和茶的具体做法了:

// 茶
public class Tea extends CaffeineBeverage {
    // 实现父类的抽象方法
    // 是自己特有的动作
    public void brew() {
        System.out.println("Steeping the tea");
    }
    public void addCondiments() {
        System.out.println("Adding Lemon");
    }
}


咖啡:

// 咖啡
public class Coffee extends CaffeineBeverage {
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }
    public void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }
}


这样做的好处:
1. 由抽象类CaffeineBeverage主导一切,它拥有算法,而且保护这个算法

2. 对子类来说,CaffeineBeverage类的存在,可以将代码的复用最大化

3. 算法只存在一个地方,所以容易修改。

4. 这个模板方法提供了一个框架,可以让其它的咖啡因因类插进来。新的咖啡因饮料只需要实现自己的方法就可以了

5. CaffeineBeverage专注在算法本身,而由子类提供完整的实现

OK了,其实上面实现的东西 就叫做模板方法模式。

定义模板方法模式

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

模板就是一个方法,更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由其子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。



模板方法的挂钩

钩子是一种被申明在抽象类中的方法,但是只有空的或者默认的实现。

钩子的存在,可以让子类有能力对算法的不同点进行挂钩。而要不要挂钩也由子类决定。

我们来看其中一个实例:带钩的咖啡因饮料类

// 带钩的咖啡因饮料类
public abstract class CaffeineBeverageWithHook {
    // 这个方法声明为final,防止子类改变模板方法中的算法
    void final 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");
    }
 
    // 这就是一个“钩子”,子类可以选择覆盖这个方法
    // 这里定义了一个方法,通常是空的缺省实现
    // 这个方法智慧返回true,即默认是加饮料的
    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;
    }
}


设计原则

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

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

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



好莱坞原则与模板方法:

当我们设计模板方法时,我们告诉子类“不要调用我们,我们会调用你”

在上面咖啡与茶的例子中,抽象类CaffeineBaverage是我们的高层组件,它能够控制冲泡法的算法,只有在需要子类实现某个方法时,才调用子类。而子类只提供实现的pourInCup和addCondiments方法,如果没有被调用,子类绝对不会直接调用抽象类。

问题

创建一个模板方法时,怎样才能知道什么时候该使用抽象方法,什么时候使用钩子呢?

答:当你的子类“必须”提供算法中某个方法或步骤的实现时,就使用抽象方法。如果算法的这个部分是可选的,就用钩子。如果是钩子的话,子类可以选择实现

使用钩子的真正目的是什么?

钩子有几种用法。如,钩子可以让子类实现算法中的可选部分,或者钩子对于子类的实现并不重要时,子类可以不理会这个钩子。钩子的另一个用法是,让子类能够有机会对模板方法中的某些即将发生的(或刚刚发生的)步骤作出反映,例如,在屏幕上显示数据等。

好莱坞原则和依赖倒置原则的关系?
依赖倒置原则教我们尽量避免使用具体类,而多用抽象。而好莱坞原则是用在创建框架或组件上的一种技巧,好让低层组件能够被挂钩进计算中,而且又不会让高层组件依赖低层组件。两者的目标都是解藕,但是依赖倒置原则更加注重如何在设计中避免依赖。好莱坞原则教我们一个技巧,一个有弹性的设计,允许低层结构能够互相操作,而又防止其他类太过依赖他们。



实际应用中的模板方法

模板方法几乎无处不在 ,这里举几个例子:

Java中的Array类的sort()模板方法,要实现comparTo();

Java中的Swing窗口程序大量的使用了模板方法:Swing中的JFrame,它是一个最基本的Swing容器,继承了一个paint()方法,这是一个钩子,它不做什么事情,通过覆盖paint(),你可以将自己的代码插入到JFrame的算法中,显示出自己想要的画面。

appleet,是一个能在网页上面执行的小程序,也提供了大量的钩子。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐