[垃圾桶]模板方法模式(5.10)

本文移到垃圾桶,参考 3.3 模板方法模式(5.10)



★模板方法模式:父类定义模板方法,子类改写流程的一部分。

如果说学习策略模式的难度系数为0,模板方法模式的难度系数则为1(10分制)。

我们观察模板方法中的每一个可变的部分(设计为抽象方法),如同在应用策略模式;另一方面,如果模板方法中只有一个可变的部分,模板方法模式还原为策略模式。因此模板方法模式可以视为策略模式的简单推论:可变部分由一到多。

因此yqj2065认为,介绍模板方法模式时,真正值得讨论的问题是:如何有效地控制和合理地使用实现继承



     已知一个方法如foo或templateMethod,其算法由一系列步骤如step1、step2、step3和step4构成,而且这些步骤按照确定的顺序执行。于是可以定义方法如下。

    public void templateMethod(){
        {/*step1*/}
        {/*step2*/}
        {/*step3*/}
        {/*step4*/}
    }

通常,将一段很长的代码分解成若干子方法如step1()、step2()、step3()和step4(),都是值得尝试的。

现在的问题是:各步骤中,有些是稳定的代码,比如说块语句step1或方法step1()是稳定,而有些是不确定的/可变的代码,如step2()、step3()和step4()有多种变化。yqj2065给出的定义:


模板方法模式(templatemethod pattern) 本身的思路很简单:模板方法定义了一个算法的框架或模板,本模式得名为模板方法模式;父类型Sup(暗示supertype)提供模板方法和可复用的稳定步骤;可变的部分,则由子类延迟实现。

【GoF 模板方法模式:定义一个算法的框架,将它的一些步骤延迟到子类中。模板方法模式使得子类可以在不改变一个算法的结构时重定义该算法的某些特定步骤。Template Method Pattern:  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.】   

 受限制的实现继承

任意一个涉及固定流程的日常生活例子,都可以用于演示模板方法模式。本节使用一般性的例子Sup类。类Sup中的模板方法templateMethod()定义了一个算法的框架。模板方法可以调用的方法,包括步骤方法、其他类中的方法、本类中的一些具体方法、某些工厂方法等等。

通常,templateMethod()以及不变步骤的代码如step1()由final修饰,不允许子类篡改/override。更彻底地,不变步骤的代码可以设计为private,如果它不被子类和外界使用的话,如private 方法 _step。

package method.templateMethod;
import static tool.Print.*;
public abstract class Sup{
    public final void step1(){ pln("Sup.step1()"); }
    public abstract void step2();
    public void step3(){pln("Sup.step3()");  }//默认实现,警惕
    public void step4(){ } //
    private void _step (){pln("Sup.mmm()");}//模板方法的某些部分步骤不需要外界和子类知道
    public final void templateMethod(){
        step1();step2();step3();this. _step () ; step4();
    }   
}

模板方法模式中,值得讨论的问题是:如何有效地控制和合理地使用实现继承

[编程导论·4.2.3接口继承 Vs. 实现继承]中,强调了接口继承(协议继承)和抽象方法的重要性,同时解剖了实现继承的缺陷,列举了子类对待父类方法的5种形式

  • 直接继承,如final方法、不准备override的其他方法。
  • 改进语意的改写。子类的改写方法中必须调用父类相同签名方法,super.m ()。
  • 替换语意的改写。完全以新的实现替换,体现了对父类方法的漠视。
  • 空方法的改写。没有代码复用的诱惑,一种特定情况下使用的设计技巧。
  • 接口继承。对抽象方法的继承,无代码可以复用。

这5种形式中,仅前2种复用了父类代码。

  • 合理地使用实现继承的典型例子,是子类直接继承父类的final方法。将各子类中共同的行为提取出来并集中到一个父类中,以避免代码重复。这一思想被称为“代码向上(父类)集中”,是面向对象程序设计中常用的技术。通常用final来凸显稳定的代码的不必修改特点。
  • 如果实在需要,父类也可以给出基本实现——原则上,需要在文档中注明,子类应该改进语意的改写(至少调用父类的代码一次),而避免替换语意的改写。
  • 但是,子类设计者可能忘记调用父类的代码,于是,父类设计者强制子类设计者采用改进语意的改写的一个技巧是设计一个小的、private的模板方法_step3()。

package method.templateMethod;
import static tool.Print.*;
public abstract class Sup{
    //其他代码略
//     public void step3(){pln("Sup.step3()");  }//默认实现,警惕
    public void step3(){}//重构
    private void _step3(){//模板方法
        pln("Sup.step3(),base");//
        step3();
}
public final void templateMethod(){
        step1();step2();_step3();//
    }   
}

给子类的选择

父类对于不稳定的部分如何设计呢?基本的选项:

  • abstract方法。不稳定的部分定义为抽象方法(C++中为纯虚函数)是首选,子类必须改写它们。
  • 空方法;空方法是抽象方法的替代物,如step4(),允许子类忽略本步骤。
  • 如果实在需要,也可以给出基本实现——文档中注明,子类应该改进语意的改写(至少调用父类的代码一次)。按照上一节的重构,可以通过强制子类设计者采用改进语意的改写,本项归结为空方法选项。

1. hook op/钩子方法问题

先让我吐槽一下《设计模式》,GoF在讲解模板方法模式时,使用hook op/钩子方法、好莱坞法则纯属找抽

钩子(hook)是(用C++等)Windows编程中常用的术语。Windows消息处理机制中,如果发生了某种事件,一般由目标窗口的处理函数处理它。当自己或其它进程发生了某种事件,应用程序所定义的钩子先捕获该消息(先得到控制权),它可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。钩子使得用户程序可以对操作系统进行控制。本质上,钩子(hook)是一个回调函数

即使在分层结构中,处于某个低层框架的父类对于不稳定的部分定义了abstract方法和空方法,它们都是回调接口/钩子(hook),上层的子类必须为abstract方法和(可选地) 为空方法提供实现。因此,[GoF]以hook op/钩子方法特指空方法,并不妥当。另外,在非分层结构中,父类的可改写的方法(即final、static和private方法之外的所有方法),通常不被称为回调接口/钩子(hook)。

好莱坞原则(参见[1.4.3]什么是好莱坞原则)清楚地说明,它用于Client-Server关系上,而类层次的父子关系上。

2. 开关方法

有时候,对于稳定的方法step1(),子类可能不需要该步骤。换言之,子类要么全盘使用该代码,要么不要该代码。这时父类可以提供开关方法。

package method.templateMethod;
import static tool.Print.*;
public abstract class Sup{
    public final void step1(){ pln("Sup.step1()"); }
    public void step3(){}//重构
    private void _step3(){
        pln("Sup.step3(),base");
        step3();
    }
    public final void templateMethod(){
        if( isExe1() ) step1();
        //step2();
        if( isExe3() )  _step3();
        //略
}
public abstract boolean isExe1();//强制必须选择
    public boolean isExe3(){//自由选择
        return true ;
    }
}

package method.templateMethod;
public class Sub extends Sup{
    /**父类强制必须选择.我选择不执行*/
    @Override public boolean isExe1(){return false;}
    /**父类默认执行step3().我接受*/    
    //其他 略
}

这里,钩子不钩子没有任何意义,如isExe1()是abstract方法,它是钩子吗?isExe3()空方法(注意:返回默认值的方法,均视为空方法。你可以设计isNotExe3()),它是钩子吗?


小结:

1.类层次中,子类可以调用父类的方法,但是父类不能够调用子类的方法。如果两者不在一个层,则父类必须在下层。
2.子类可以对父类的方法进行改进语意的改写、替换语意的改写、对空方法的改写——前两者尽量避免。《设计模式》中的钩子,在本模式中,“应该”是父类定义的空方法。(如果在本模式中钩子仅仅表示可被改写的方法,而非特指空方法的话,这里的钩子就是一个麻袋,装啊装啊装)

模板方法定义了一个过程的流程,并将其中可变的部分设计成abstract方法。子类能够①以直接继承的方式复用父类的(不变的部分)代码(最好以final修饰),②以接口继承的方式为可变的部分提供延迟实现。

任何其他的用法,例如将可变的部分设计普通方法并提供默认代码,都是对模板方法模式的滥用。

例子

参考【编程导论·7.1.3自定义类装载器】,ClassLoader的核心是loadClass()方法就是模板方法

日常生活的例子,你自己想一个涉及某些特定步骤的场景(如做饭的洗煮吃,洗米/洗菜,……),顺便可以写出演示代码。





  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值