java设计模式之模板方法模式

Posted 程序编织梦想

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java设计模式之模板方法模式相关的知识,希望对你有一定的参考价值。

注意 本博客摘录于秦小波老师写的《设计模式之禅》

模板方法模式

汽车厂造悍马

假设我们是一个汽车公司,现在有客户来了,要求我们造悍马! 既然上级下来命令那就造呗,但是造悍马你得告诉我们汽车有什么功能啊,客户说了:“能启动车,能停止车,能响,能跑。”好,功能出来了,开始造汽车了。类图如下:
技术分享

抽象悍马模型代码如下:

public abstract class HummerModel {
    /*
    * 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
    * 是要能够发动起来,那这个实现要在实现类里了
    */
    public abstract void start();

    //能发动,还要能停下来,那才是真本事
    public abstract void stop();

    //喇叭会出声音,是滴滴叫,还是哔哔叫
    public abstract void alarm();

    //引擎会轰隆隆地响,不响那是假的
    public abstract void engineBoom();

    //那模型应该会跑吧,别管是人推的,还是电力驱动的,总之要会跑
    public abstract void run();
}

在抽象类中,我们定义了悍马模型都必须具有的特质:能够发动、停止,喇叭会响,引擎可以轰鸣,而且还可以停止。但是每个型号的悍马实现是不同的。

H1型号的悍马代码下所示:

public class HummerH1Model extends HummerModel {
    //H1型号的悍马车鸣笛
    public void alarm() { 
        System.out.println("悍马H1鸣笛...");
    }
    //引擎轰鸣声
    public void engineBoom() {
         System.out.println("悍马H1引擎声音是这样的...");
    }
    //汽车发动
    public void start() { 
        System.out.println("悍马H1发动...");
    }
    //停车
    public void stop() { 
        System.out.println("悍马H1停车...");
    }
    //开动起来
    public void run(){
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
        this.alarm();
        //到达目的地就停车
        this.stop();
    }
}

大家注意看run()方法,这是一个汇总的方法,一个模型生产成功了,总要拿给客户检测吧,怎么检测?“是骡子是马,拉出去溜溜”,这就是一种检验方法,让它跑起来!通过run()这样的方法,把模型的所有功能都测试到了。

H2型号悍马如下代码所示。

public class HummerH2Model extends HummerModel {
    //H2型号的悍马车鸣笛
    public void alarm() { 
        System.out.println("悍马H2鸣笛...");
    }
    //引擎轰鸣声
    public void engineBoom() { 
        System.out.println("悍马H2引擎声音是这样在...");
    }
    //汽车发动
    public void start() { 
        System.out.println("悍马H2发动...");
    }
    //停车
    public void stop() { 
        System.out.println("悍马H2停车...");
    }
    //开动起来
    public void run(){
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
        this.alarm();
        //到达目的地就停车
        this.stop();
    }
}

好了,程序编写到这里,已经发现问题了,两个实现类的run()方法都是完全相同的,那这个run()方法的实现应该出现在抽象类,不应该在实现类上,抽象是所有子类的共性封装。
注意 在软件开发过程中,如果相同的一段代码复制过两次,就需要对设计产生怀疑, 架构师要明确地说明为什么相同的逻辑要出现两次或更多次。
好,问题发现了,我们就需要马上更改,修改后的类图下所示:
技术分享

注意 抽象类HummerModel中的run()方法,由抽象方法变更为实现方法,其源代码如下所示。

修改后的抽象悍马模型

public abstract class HummerModel {
    /*
    * 首先,这个模型要能发动起来,别管是手摇发动,还是电力发动,反正
    * 是要能够发动起来,那这个实现要在实现类里了
    */
    public abstract void start();
    //能发动,还要能停下来,那才是真本事
    public abstract void stop();
    //喇叭会出声音,是滴滴叫,还是哔哔叫
    public abstract void alarm();
    //引擎会轰隆隆地响,不响那是假的
    public abstract void engineBoom();
    //那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑
    public void run(){
        //先发动汽车
        this.start();
        //引擎开始轰鸣

        this.engineBoom();
        //然后就开始跑了,跑的过程中遇到一条狗挡路,就按喇叭
        this.alarm();
        //到达目的地就停车
        this.stop();
    }
}

场景类实现的任务就是把生产出的模型展现给客户,场景类代码如下:

public class Client {
    public static void main(String[] args) {
        //XX公司要H1型号的悍马
        HummerModel h1 = new HummerH1Model();
        //H1模型演示
        h1.run();
    }
}

运行结果如下所示。

悍马H1发动… 悍马H1引擎声音是这样的… 悍马H1鸣笛…
悍马H1停车…

目前客户只要看H1型号的悍马车,没问题,生产出来,同时可以运行起来给他看看。 非常简单,那如果我告诉你这就是模板方法模式你会不会很不屑呢?就这模式,太简单了, 我一直在使用呀!是的,你经常在使用,但你不知道这是模板方法模式,那些所谓的高手就可以很牛地说:“用模板方法模式就可以实现”,你还要很崇拜地看着,哇,牛人,模板方法 模式是什么呀?这就是模板方法模式。

注意 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。

模板方法模式的使用场景

● 多个子类有公有的方法,并且逻辑基本相同时。

● 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个 子类实现。

● 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通 过钩子函数(见“模板方法模式的扩展”)约束其行为。

模板方法模式的扩展

目前为止,这两个模型都稳定地运行,突然有一天,领导急匆匆地找到了我:“看你怎么设计的,车子一启动,喇叭就狂响,吵死人了!客户提出H1型号的悍马喇叭 想让它响就响,H2型号的喇叭不要有声音,赶快修改一下。”
自己惹的祸,就要想办法解决它,稍稍思考一下,解决办法有了,先画出类图如下:
技术分享

类图改动似乎很小,在抽象类HummerModel中增加了一个实现方法isAlarm,确定各个型 号的悍马是否需要声音,由各个实现类覆写该方法,同时其他的基本方法由于不需要对外提 供访问,因此也设计为protected类型。其源代码如下所示:

扩展后的抽象模板类

public abstract class HummerModel {
    /*
    * 首先,这个模型要能够被发动起来,别管是手摇发动,还是电力发动,反正
    * 是要能够发动起来,那这个实现要在实现类里了
    */
    protected abstract void start();
    //能发动,还要能停下来,那才是真本事
    protected abstract void stop();
    //喇叭会出声音,是滴滴叫,还是哔哔叫
    protected abstract void alarm();
    //引擎会轰隆隆的响,不响那是假的
    protected abstract void engineBoom();
    //那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑
    final public void run() {
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //要让它叫的就是就叫,喇嘛不想让它响就不响
        if(this.isAlarm()){
            this.alarm();
        }
        //到达目的地就停车
        this.stop();
    }
    //钩子方法,默认喇叭是会响的
    protected   boolean isAlarm(){
    return true;
    }
}

在抽象类中,isAlarm是一个实现方法。其作用是模板方法根据其返回值决定是否要响喇叭,子类可以覆写该返回值,由于H1型号的喇叭是想让它响就响,不想让它响就不响,由人控制,其源代码如下所示。

扩展后的H1悍马

public class HummerH1Model extends HummerModel {
    private boolean alarmFlag = true;   //要响喇叭
    protected void alarm() { 
        System.out.println("悍马H1鸣笛...");
    }
    protected void engineBoom() { 
        System.out.println("悍马H1引擎声音是这样的...");
    }
    protected void start() { 
        System.out.println("悍马H1发动...");
    }
    protected void stop() { 
        System.out.println("悍马H1停车...");
    }
    protected boolean isAlarm() {
        return this.alarmFlag;
    }
    //要不要响喇叭,是由客户来决定的
    public void setAlarm(boolean isAlarm){
        this.alarmFlag = isAlarm;
    }
}

只要调用H1型号的悍马,默认是有喇叭响的,当然你可以不让喇叭响,通过isAlarm(false)就可以实现。H2型号的悍马是没有喇叭声响的,其源代码如代码下所示。

扩展后的H2悍马

public class HummerH2Model extends HummerModel {
    protected void alarm() {
         System.out.println("悍马H2鸣笛...");
    }
    protected void engineBoom() { 
        System.out.println("悍马H2引擎声音是这样的...");
    }
    protected void start() {
         System.out.println("悍马H2发动...");
    }
    protected void stop() {
         System.out.println("悍马H2停车...");
    }
    //默认没有喇叭的
    protected boolean isAlarm() {
        return false;
    }
}

H2型号的悍马设置isAlarm()的返回值为false,也就是关闭了喇叭功能。场景类代码如下所示。

扩展后的场景类

public class Client {
    public static void main(String[] args) throws IOException{ 
        System.out.println("-------H1型号悍马--------"); 
        System.out.println("H1型号的悍马是否需要喇叭声响?0-不需要   1-需要");
        String type=(new BufferedReader(new InputStreamReader(System.in))). HummerH1Model h1 = new HummerH1Model();

        if(type.equals("0")){
            h1.setAlarm(false);
        }
        h1.run();
        System.out.println("\n-------H2型号悍马--------");
        HummerH2Model h2 = new HummerH2Model();
        h2.run();
    }
}

运行是需要交互的,首先,要求输入H1型号的悍马是否有声音,如下所示:

-------H1型号悍马-------- H1型号的悍马是否需要喇叭声响?0-不需要 1-需要输入“0”后的运行结果如下所示:
-------H1型号悍马-------- H1型号的悍马是否需要喇叭声响?0-不需要 1-需要
0
悍马H1发动... 悍马H1引擎声音是这样的... 悍马H1停车...
-------H2型号悍马-------- 悍马H2发动... 悍马H2引擎声音是这样的... 悍马H2停车...

输入“1”后的运行结果如下所示:

-------H1型号悍马-------- H1型号的悍马是否需要喇叭声响?0-不需要 1-需要    
1
悍马H1发动...
悍马H1引擎声音是这样的...
悍马H1鸣笛...
悍马H1停车...
-------H2型号悍马-------- 悍马H2发动... 悍马H2引擎声音是这样的... 悍马H2停车...

看到没,H1型号的悍马是由客户自己控制是否要响喇叭,也就是说外界条件改变,影响到模板方法的执行。在我们的抽象类中isAlarm的返回值就是影响了模板方法的执行结 果,该方法就叫做钩子方法(Hook Method)。有了钩子方法模板方法模式才算完美,大家 可以想想,由子类的一个方法返回值决定公共部分的执行结果,是不是很有吸引力呀!

模板方法模式就是在模板方法中按照一定的规则和顺序调用基本方法,具体到前面那个例子,就是run()方法按照规定的顺序(先调用start(),然后再调用engineBoom(),再调用 alarm(),最后调用stop())调用本类的其他方法,并且由isAlarm()方法的返回值确定run()中的 执行顺序变更。








以上是关于java设计模式之模板方法模式的主要内容,如果未能解决你的问题,请参考以下文章

《Java设计模式》之模板方法模式

Java设计模式之模板方法模式(Template Method)

《设计模式之禅》之模板方法模式

Java设计模式(16)——行为模式之模板方法模式(Template)

Java设计模式之模板方法模式(Template)

设计模式之模板方法模式