例说模板方法模式(Template Method Pattern)

来源:互联网 发布:农业部渔船数据科 编辑:程序博客网 时间:2024/06/10 09:27
       设计模式中有些经典模式,放到现实生活中也是处处可见,很多年前周星星拍了一部喜剧之王,其中有这么一个场景,周同学去试镜一段戏,白鸽飞起,吴宇森式教堂枪战,而我们的DS周在“啊”的一声倒地后,又颤颤巍巍站了起来。。好吧,扯远了,本文是要总结模板方法模式,DS周一会再说,先来看看模板方法模式是啥。
 
   官方定义
       该模式定义了一个操作中的算法的骨架,将一些步骤延迟到子类,使得子类可以不改变一个算法的结构而重定义该算法的某些步骤。 
 
   翻译过来的意思是
        1 父类中定义了这么一个算法(模板方法),包含很多特定顺序的步骤
        2 有些步骤(原语操作)的实现留到子类中去实现,其它固定步骤(具体方法)在父类中实现。
        3 子类不可以改变该算法实现步骤的顺序——减少环状依赖。
 
   举例
       在周星星试镜的那段戏里,剧本可以看做是一套定义了连续步骤的算法,也就是模板方法,该剧本包括三个连续步骤1 白鸽飞起 2 教堂枪战 3 群众演员保持假死状态,前两个步骤是剧本中就写好的,也就是说无论NG多少遍,都是这么演,这两个步骤叫做具体方法,定义在剧本(父类)中,第3个步骤群众演员保持假死,就和特定的群众演员(子类)相关了,普通的群众演员多会直接倒地,等导演喊“cut”,周星星则比较磊落,想要喊一声“啊”在扑街,不同的子类有不同实现,但是子类们仅能想着如何拍好假死,至于整个剧本的步骤顺序,他们是木有发言权的。
   剧本类   
public abstract class SceneMaker {    //模板方法——步骤已固定    public final void startAction() {flyDove();startGunFight();keepDead();    }    // 具体方法——放鸽子    private void flyDove(){System.out.println("Dove fly");    }    // 具体方法——开始枪战    private void startGunFight(){System.out.println("Start Gun Fight...");    }    //原语操作——群众演员保持假死    protected abstract void keepDead();}
   子类一:普通群众演员   
public class ActorPeople extends SceneMaker{    @Override    //重写原语操作——正常倒地    protected void keepDead() {// TODO Auto-generated method stubSystem.out.println("keep dead");    }}
   子类二:群众演员周
public class ActorChoi  extends SceneMaker{    @Override    protected void keepDead() {// TODO Auto-generated method stub//重写原语操作——大喊一声“啊”后死亡System.out.println("say 'A' and keep dead");    }}   
   测试代码运行结果:

       不同的群众演员(子类)有不同的实现方式,但是保持不变的是剧本定义的演出步骤,先放鸽子再枪战,算法骨架已经在父类中写好,演员们是不可以随意改变的,这样来看,好像留给演员们的空间过于狭窄了,钩子操作被引入来解决这个问题。
 
   官方定义:它提供缺省的行为,子类可以在必要时进行扩展,一个钩子操作在缺省时是空操作。
 
   翻译意思是:
       1 父类的模板方法中,有些步骤并不是子类必须实现的。
       2 这些步骤在缺省情况下多是空操作,当然也可以非空。
 
   举例
       接着上面的例子,普通群众在倒地后,通常会屏住呼吸,等待导演喊“cut”在站立起来,而周同学显然不是,他觉得自己作为一个有责任心的演员,好像应该做些什么,于是在导演喊“cut”之前就颤颤巍巍站了起来,想“挣扎一下,然后再死”,整个过程可以抽象成一个钩子方法,默认实现是“保持不动,等“cut”而立,而群众演员周星星则重写了该方法——“站起来,给导演提意见”,这样分析以后,代码如下:
   剧本类
public abstract class SceneMaker {    //模板方法——步骤已固定    public final void startAction() {    flyDove();    startGunFight();    keepDead();    waitToStandUp();    }    // 具体方法——放鸽子    private void flyDove(){    System.out.println("Dove fly");    }    // 具体方法——开始枪战    private void startGunFight(){    System.out.println("Start Gun Fight...");    }    /**    * 钩子方法    * 默认: 等待导演喊“cut”然后站起来——普通群众的做法    */    protected void waitToStandUp(){    System.out.println("Waiting for ''cut' order and then stand up");    }    //原语操作——群众演员保持假死    protected abstract void keepDead();}
  子类一保持不变,大家都是普通群众,使用默认钩子方法。
  子类二:群众演员周
public class ActorChoi  extends SceneMaker{    @Override    protected void keepDead() {// TODO Auto-generated method stub//重写原语操作——大喊一声“啊”后死亡System.out.println("say 'A' and keep dead");    }    @Override    protected void waitToStandUp() {// TODO Auto-generated method stub// super.waitToStandUp(); 不再使用默认钩子方法,直接起来并向导演提意见。System.out.println("stand up directly and make device to Director");    }}
   运行测试方法结果:

 
        原语操作和钩子方法的区分,主要是子类必须重写和可以重写的区别,对于模板算法中的步骤,如果这个步骤是必须存在且没有默认实现的,就是原语操作,而对于那些未必需要子类重写的操作,可以使用钩子。
 
       以上是关于模板方法模式的实践学习,易懂但是不够深入,枯燥的理论总结免不了,这里的总结主要是来自Head First设计模式和GOF4两本书,概述性的总结读书心得,有兴趣的童鞋可以自己翻书(废话)。             
      
模式特点  
         1   提供了对算法步骤的完整管理,由基类控制算法实现的步骤。
         2   如果新添加子类,不需要改变算法的实现步骤,仅需要实现特定的方法即可。
         3   不同的子类有不同的实现,完成了对模板方法的实现。
         4   基类对算法的掌握,能够很好地实现代码复用,符合开闭原则。
         5   算法步骤仅仅存在基类中,利于修改维护,不需要再多个子类中添加重复代码。  
         6   模板方法调用的原语操作需被定义为保护函数,以供子类重写及防止其它对象调用。
 
钩子方法
        1 在基类中添加钩子方法,判断条件可以在基类中默认设置,如果子类确定需要,重写钩子方法后返回true or false 来判断是否执行对于操作。
        2 多用在一些需要视子类情况而定的算法步骤中。这样可以给与子类一定的自主权去选择是否执行模板方法中的某些步骤。
        3 定义在一个抽象基类或者接口中的空实现或者是默认实现,通常钩子方法是可选的,由各个子类来选择是否实现或者怎样实现。
        注意:可以将模板步骤中不是必要的方法用钩子实现,而对于必要的方法留待子类实现时应定义成absract函数。
        4 钩子的形象:可以挂任何东西,也可以挂默认的东西,子类来决定是否需要挂东西。   
 
模式结构
 
         
        模板方法模式UML图
      翻译成代码结构如下图所示:
 
实现
      1 访问控制修饰,确保模板及原语操作不被非法使用。
      2 尽量减少原语操作,需要重定义的操作越多,客户程序越冗长。
      3 可以将原语操作命名成特定格式,比如以“Do”作为前缀。
 
好莱坞原则——“不要打电话给我,我会打电话给你”
       高层(父类模板方法)调用低层(子类集合),低层尽量不要调用高层。可以模板方法中加上final修饰符以防止子类重写。
       依赖倒置原则侧重于如何在设计上避免依赖,通过使用抽象类而非具体类,好莱坞原则框架设计时使用的解耦技巧,适用于避免高层组件和低层组件产生环状依赖。
用模板方法排序
       Arrays中的mergeSort方法作为模板方法,静态方法要求排序对象是一个实现了compareTo方法的对象数组,任何排序对象只要实现了compareTo接口,都可以参与排序。
 
相关模式
       策略模式使用的组合方式,复用的是算法全部,可在运行时进行算法切换;模板模式多使用继承,复用的是算法的步骤,用于算法在不同子类中的具体实现。
 
代码举例
       在Eclipse中新建1个Android程序,在MainActivity中ALT+SHIFT+S,选择Override/Implement Methods,你可以看到Activity类里可以供你实现的保护方法,我们在进行Android应用层开发时重写onCreate、onStart等一些列生命周期的方法,都是钩子操作,Activity在启动时系统会顺序调用这些方法,相当于已经写好了的剧本,上层开发只需要在这些步骤中加上不同的实现即可,这些启动步骤都已经固定,可以看出模板方法在框架设计中发挥重要作用。
      
       设计模式理论学习比较抽象,想深入理解这些模式还需要在实践中多多摸索,结合实际能更好理解模式的内涵,以后的模式总结还是会采用先举例后理论的步骤,以较为容易理解的方式分享整理~
 
       版权所有~~转载请注明~~