23种设计模式之--模板方法模式
故事:
辉煌工程—制造悍马
周三一上班,老大就跑过来叫我开会,原来是一个好消息,老大终于把XX模型公司的口子打开了,要我们做悍马模型,所以老大信心满满,说一定要把这个做好,但是开发只有一周的时间,我说这要分析,设计,测试,还要考虑各种性能,一周做不完啊, 老大说只做最基本的实现就可以,既然老大都发话了,那只能拼命做吧。
既然领导都说了,不考虑扩展性,那好办,先按照最一般的经验设计类图:
public abstract class HummerModel { // start car public abstract void start(); // stop car public abstract void stop(); // press Klaxon when you need public abstract void alarm(); // startup engine public abstract void engineBoom(); // run car public void run() { // start car start(); // startup engine engineBoom(); // press Klaxon when you need alarm(); // stop car stop(); } }
public class HummerH1 extends HummerModel { @Override public void alarm() { System.out.println( "HummerH1 alarming." ); } @Override public void engineBoom() { System.out.println( "HummerH1 startuping engineBoom." ); } @Override public void start() { System.out.println( "HummerH1 starting." ); } @Override public void stop() { System.out.println( "HummerH1 stoping." ); } }
public class HummerH2 extends HummerModel { @Override public void alarm() { System.out.println( "HummerH2 alarming." ); } @Override public void engineBoom() { System.out.println( "HummerH2 startuping engineBoom." ); } @Override public void start() { System.out.println( "HummerH2 starting." ); } @Override public void stop() { System.out.println( "HummerH2 stoping." ); } }
public class Client { public static void main( String[] args ) { HummerModel hummerH1 = new HummerH1(); hummerH1.run(); HummerModel hummerH2 = new HummerH2(); hummerH2.run(); } }
这就是模板方法,看起来是不是很熟悉,呵呵,我们重构时,经常这么干。
定义:
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
定义一个操作中的算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
通用类图:
注意:为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。
public abstract class AbstractClass { // base method protected abstract void doSomething(); // base method protected abstract void doAnything(); // template method public void templateMethod() { //invoking base method doSomething(); doAnything(); } }
public class ConcreateClass1 extends AbstractClass { //implements base methods @Override protected void doAnything() { // handel business } @Override protected void doSomething() { // handel business } }
public class ConcreateClass2 extends AbstractClass { // implements base methods @Override protected void doAnything() { // handel business } @Override protected void doSomething() { // handel business } }
public class Client { public static void main( String[] args ) { AbstractClass concreateClass1 = new ConcreateClass1(); AbstractClass concreateClass2 = new ConcreateClass2(); // invoking template method concreateClass1.templateMethod(); concreateClass2.templateMethod(); } }
注意: 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。
优点:
1.封装不变部分, 扩展可变部分。
把认为是不变的部分的算法封装到父类实现, 而可变部分的则可以通过继承来继续扩展。
2.提取公共代码, 便于维护。
3.行为由父类控制 ,子类实现。
缺点:
按照我们的设计习惯, 抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的事物属性和方法。但是模板方法模式去颠倒了,这对新手来说,会带来代码阅读的难度。
使用场景:
1.多个子类有公有的方法,并且逻辑基本相同时。
2.重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。
3.重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数(见“模板方法模式的扩展”)约束其行为。
模板方法模式的扩展
到目前为止, 两个模型都运行稳定,突然有一天, 老大急匆匆地找到了我:
“看你怎么设计的,车子一启动,喇叭就狂响,吵死人了,客户提出H1型号的悍马喇叭想让它响就响,H2型号的喇叭不要有声音,赶快修改一下。”
自己惹的祸,就要想办法解决它,稍稍思考一下,解决办法有了, 先画出类图
public abstract class HummerModel { private boolean alarmFlag = true; // start car public abstract void start(); // stop car public abstract void stop(); // press Klaxon when you need public abstract void alarm(); // startup engine public abstract void engineBoom(); // is need alarm protected boolean isAlarm() { // defult need return alarmFlag; } // customer decision is need alarm public void setAlarm( boolean isAlarm ) { alarmFlag = isAlarm; } // run car public void run() { // start car start(); // startup engine engineBoom(); // press Klaxon when you need if ( isAlarm() ) { alarm(); } // stop car stop(); } }
public class HummerH2 extends HummerModel { @Override public void alarm() { System.out.println( "HummerH2 alarming." ); } @Override public void engineBoom() { System.out.println( "HummerH2 startuping engineBoom." ); } @Override public void start() { System.out.println( "HummerH2 starting." ); } @Override public void stop() { System.out.println( "HummerH2 stoping." ); } @Override protected boolean isAlarm() { return false; } }
setAlarm是一个钩子方法,用户可以通过这个方法,控制喇叭是否要响。
最佳实践
父类调用子类的方法的三种方式:
1. 把子类传递到父类的有参构造中,然后调用。
2. 使用反射的方式调用, 你使用了反射还有谁不能调用的?!
3. 父类调用子类的静态方法
父类调用子类的方法其实是不允许的,这个问题我们可以换个角度去理解, 父类建立框架,子类在重写了父类部分的方法后,再调用从父类继承的方法,产生不同的结果(这正是模板方法模式)。