设计模式学习(九)——模板方法模式

来源:互联网 发布:java经典程序 源代码 编辑:程序博客网 时间:2024/05/18 02:12

参考书——《HeadFirst设计模式》

和书中一样,用冲咖啡和沏茶来说这个设计模式


       冲咖啡:                     沏茶:                                    制作饮料:

step1       烧水                       烧水                                       烧水

step2      用沸水冲咖啡粉         用沸水冲茶叶      ===========》               用沸水冲

step3     倒入杯中               倒入杯中                                      倒入杯中

step4     加糖加奶               加柠檬                                        加适当的调料


我们看到step1和step3是一样的,换而言之,可以具体实现,step2和step4虽然不一样但是我们可以进行提取,也就是抽象。等等,想到了什么?!抽象类   P.s:抽象类和接口的区别http://dev.yesky.com/436/7581936.shtml 

public abstract class Beverage {final void prepareRecipe() {boilwater();brew();pourIncup();addCondiments();}abstract void brew();abstract void addCondiments();void boilwater() {System.out.println("烧水");}    void pourIncup() {    System.out.println("倒入杯中");}}


模板方法——就是这里的prepareRecipe(),它定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。

“炫酷的地方”:1)Beverage类主导一切,它拥有这个算法,并且保护这个算法;

                2)对子类来说,Beverage类的存在,可以提高代码的复用;

                3)算法存在一个地方,便于维护;

                4)模板方法提供了一个框架,可以让其他的饮料插进来,新的饮料只要实现自己的方法就行;

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


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


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


 有了这个抽象类,我们就可以写具体的Coffee和Tea类了,代码如下:

public class Coffee extends Beverage {void brew() {System.out.println("用沸水冲咖啡");    }    void addCondiments() {System.out.println("加糖加奶");    }}

到这里,发现问题了,冲杯Coffee,前三个步骤是必须的,但是添加调料就不是必须的了,有的人不喜欢给咖啡里加糖加奶,怎么办?


    基于这个问题我们引出"钩子"方法

    在上面的链接的这篇文章里,有一段话“在abstract class的定义中,我们可以赋予方法的默认行为。

    对此我的理解:给一个方法赋予默认行为,这个方法是子类可以重写也可以不重写,子类可以通过重写来提升方法的性能,这样的方法就可以成为“钩子”方法。有人会说,普通的方法(没有被final修饰)也可以做到可以重写也可以不重写啊。难道都是“钩子”方法?!

    在别人的博客看到这样的话:“继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏

    所以,我觉得“钩子”方法和普通方法,其实是个度的问题,普通方法,就如上面这段话的红色字所说,它有它自己规定的契约,不太希望被改变。而“钩子”方法是规定了默认行为(这个默认行为甚至可以为空,“钩子”方法什么都不做),最为宽泛的,子类可以在其上进行规定。


接下来,我们看看如何用"钩子"方法解决上面的问题——有的人不喜欢给咖啡里加糖加奶,怎么办?

先来看Beverage类:

public abstract class Beverage {final void prepareRecipe() {boilwater();brew();pourIncup();if(customerWantsCondiments()){addCondiments();}}abstract void brew();abstract void addCondiments();void boilwater() {System.out.println("烧水");}    void pourIncup() {    System.out.println("倒入杯中");}        boolean customerWantsCondiments() {    return true;    }}

这个customerWantsCondiments()就是一个“钩子”方法,只会返回true,不做别的事,子类可以覆盖也可以不覆盖。


再来看Coffee类:


public class Coffee extends Beverage {void brew() {System.out.println("用沸水冲咖啡");    }    void addCondiments() {System.out.println("加糖加奶");    }boolean customerWantsCondiments() {    String answer = getUserInput();    if(answer.toLowerCase().startsWith("y")){    return true;    }else{    return false;    }    }public String getUserInput() {String answer = null;System.out.println("需要加糖加奶么?(y/n)");BufferedReader in = new BufferedReader(new InputStreamReader(System.in));try{answer = in.readLine();}catch(IOException e){System.out.println("抱歉,出错了!");}if(answer==null){answer = "no";    }return answer;}}

问题解决了!“钩子”方法竟然能够作为条件控制,影响抽象类中的算法流程,实在不赖吧微笑

这里我们总结下“钩子”方法的几个作用:

1)可以让子类实现算法中可选的部分;

2)在“钩子”方法对子类的实现并不重要的时候,子类可以对它置之不理;

3)让子类能够有机会对模板方法中即将发生的(or刚刚发生的)步骤做出反应;

4)也可以让子类有能力为抽象类做一些决定(上面的代码用的就是这个)。


P.S:

好莱坞原则:别调用我们,我们会调用你。

在好莱坞原则下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。也就是说高层组件对低层组件采用了好莱坞原则。


好莱坞原则和模板方法模式

还是用我们上述的例子来说。

Beverage是我们的高层组件,它能够控制饮料的冲泡算法,只有在需要子类实现某个方法时,才调用子类。

饮料的客户代码只依赖Beverage抽象,而不依赖具体的Tea或Coffee,这可以减少整个系统的依赖。









0 0
原创粉丝点击