23种设计模式-模板方法模式

2019-05-20  本文已影响0人  stayiwithime
  1. 辉煌工程-制造悍马

原书中作者说的公司是做模型生产的,举的例子是做车辆的模型,先不考虑扩展性,设计类图10-1:


10-1

这个设计其实就基本上是最基础的,先定义出车模型的接口,然后各种模型实现该接口就好了,代码如下:

public abstract class HummerModel {
    //能发动
    public abstract void start();
    //停
    public abstract void stop();
    //喇叭
    public abstract void alarm();
    //引擎响
    public abstract void engineBoom();
    //跑
    public abstract void run();
}
public class HummerH1Model extends HummerModel {
    @Override
    public void start() {
        System.out.println("悍马H1发动。。。");
    }

    @Override
    public void stop() {
        System.out.println("悍马H1停车。。。");
    }

    @Override
    public void alarm() {
        System.out.println("悍马H1鸣笛。。。");
    }

    @Override
    public void engineBoom() {
        System.out.println("悍马H1引擎声音。。。");
    }

    @Override
    public void run() {
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //按喇叭
        this.alarm();
        //停车
        this.stop();
    }
}

public class HummerH2Model extends HummerModel {
    @Override
    public void start() {
        System.out.println("悍马H2发动。。。");
    }

    @Override
    public void stop() {
        System.out.println("悍马H2停车。。。");
    }

    @Override
    public void alarm() {
        System.out.println("悍马H2鸣笛。。。");
    }

    @Override
    public void engineBoom() {
        System.out.println("悍马H2引擎声音。。。");
    }

    @Override
    public void run() {
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //按喇叭
        this.alarm();
        //停车
        this.stop();
    }
}

这里很明显的感觉就是,两个子类的run方法都是完全相同的,那么这个run方法的实现应该出现在抽象类中,不应该在实现类中,抽象是所有一类的共性封装。

注意 在软件开发过程中,如果一段代码复制过两次,就需要对设计产生怀疑,架构师要明确的说明为什么相同的逻辑要出现两次或更多次

修改类图如下:


10-2

代码修改如下:

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 HummerH1Model extends HummerModel {
    @Override
    public void start() {
        System.out.println("悍马H1发动。。。");
    }

    @Override
    public void stop() {
        System.out.println("悍马H1停车。。。");
    }

    @Override
    public void alarm() {
        System.out.println("悍马H1鸣笛。。。");
    }

    @Override
    public void engineBoom() {
        System.out.println("悍马H1引擎声音。。。");
    }
}
public class HummerH2Model extends HummerModel {
    @Override
    public void start() {
        System.out.println("悍马H2发动。。。");
    }

    @Override
    public void stop() {
        System.out.println("悍马H2停车。。。");
    }

    @Override
    public void alarm() {
        System.out.println("悍马H2鸣笛。。。");
    }

    @Override
    public void engineBoom() {
        System.out.println("悍马H2引擎声音。。。");
    }
}

这就是简单的模板方法模式,将确定的执行顺序放在抽象类中,实现类只需要实现具体实现步骤中的方法就好了。

  1. 模板方法模式的定义

模板方法模式(Template Method Pattern) 是如此简单,其定义如下:
Define the skeleton of algorthm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。)

模板方法模式的通用类图10-3:

10-3
模板方法确实非常简单,仅仅使用了java的继承机制,但它是一个应用非常广泛的模式。其中AbstractClass叫做抽象模板,他的方法分为两类:

注意 为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。

在类图中还有 一个角色:具体模板,ConcreteClass1和ConcreteClass2,实现抽象模板中的基本方法。

注意 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为public类型,实现类若非必要,尽量不要扩大父类中的访问权限

  1. 模板方法模式的应用

3.1模板方法模式的优点

3.2 模板方法模式的缺点
这个很明显,按照我们的设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的失误属性和方法。但这里确实是子类的实现方式对父类产生了影响,基本方法的实现是在子类中,但是流程执行方法是由父类的方法执行的。在复杂的项目中,会带来代码阅读的难度。

3.3模板方法模式的使用场景

  1. 模板方法模式的扩展

到目前为止,例子中两个模型都稳定的运行,突然有一天,需求有了变化,H1的喇叭想让他响就响,H2型号的喇叭不要有声音,类图10-4:


10-4

类图的改动很小,就在HUmmerModel中增加了一个实现方法isAlarm,确定个型号的悍马是否需要声音,代码如下:

//抽象模板
public abstract class HummerModel {
    //能发动
    protected abstract void start();
    //停
    protected abstract void stop();
    //喇叭
    protected abstract void alarm();
    //引擎响
    protected abstract void engineBoom();
    //跑
    public final void run(){
        //先发动汽车
        this.start();
        //引擎开始轰鸣
        this.engineBoom();
        //按喇叭
        if(this.isAlarm()){
            this.alarm();
        }
        //停车
        this.stop();
    }
    protected boolean isAlarm(){
        return true;
    }
}
//具体模板1
public class HummerH1Model extends HummerModel {
    private boolean alarmFlag = true; //要响喇叭
    @Override
    protected void start() {
        System.out.println("悍马H1发动。。。");
    }

    @Override
    protected void stop() {
        System.out.println("悍马H1停车。。。");
    }

    @Override
    protected void alarm() {
        System.out.println("悍马H1鸣笛。。。");
    }

    @Override
    protected void engineBoom() {
        System.out.println("悍马H1引擎声音。。。");
    }

    @Override
    protected boolean isAlarm() {
        return this.alarmFlag;
    }
    public void setAlarmFlag(boolean alarmFlag){
        this.alarmFlag = alarmFlag;
    }
}
//具体模板2
public class HummerH2Model extends HummerModel {
    @Override
    protected void start() {
        System.out.println("悍马H2发动。。。");
    }

    @Override
    protected void stop() {
        System.out.println("悍马H2停车。。。");
    }

    @Override
    protected void alarm() {
        System.out.println("悍马H2鸣笛。。。");
    }

    @Override
    protected void engineBoom() {
        System.out.println("悍马H2引擎声音。。。");
    }

    @Override
    protected boolean isAlarm() {
        return false;
    }
}

H1型号的悍马是由客户控制是否要响喇叭,也就是说外界条件改变,影响到模板方法的执行。在抽象类中isAlarm的返回值就是影响了模板方法的执行结果,该方法就叫钩子方法(Hook Method)。(这个模板方法模式就是典型的子类方法侵入了父类,改变了父类的执行结果,其实这是有问题的,这也是模板方法的缺点吧。但是往后面看作者的解释,这个好像并不是多大的问题,反而认为模板方法模式的这种处理方式算是比较好的处理了父类依赖子类的场景。果然原则都不能学死,还是得看具体的情景

  1. 最佳实践

"父类怎么调用子类的方法"。这个问题很有普遍性,父类是否可以调用子类的方法呢?书中认为能,但是强烈、极度的不建议这么做,父类调用子类的方法如下:

内容来自《设计模式之禅》

上一篇 下一篇

猜你喜欢

热点阅读