我使用过一个简单的后台服务框架.这个框架上手很容易,我只需要继承一个基类,同时实现,或重写(override)基类声明的几个接口(这些接口声明为虚函数,或者纯虚函数),然后调用基类定义好的run()函数,便可以将框架代码运行起来.run函数做的事情,是依序调用上述的几个接口:


class Service {
public :
int run(){
// ....
step1(); // 收包 , 解包
step2(); // 业务逻辑处理
step3(); // 回包
step4(); //资源释放
//....
}
protected:
virtual int step1(){
// 收包,解包逻辑实现
//... }
virtual int step3(){
// 回包逻辑实现
//...
}
virtual int step4(){
//资源释放
//...
}
virtual int step2() =0 ; //纯虚函数,派生类实现
}

其中收包,解包,回包,释放资源等动作,框架会提供一份实现,由于我们有时候会采用其他的数据协议,所以基类也将收包回包等函数声明为虚函数,允许我们针对新的协议进行函数的重写(override).而对于业务逻辑处理函数,也就是step2,框架无法为我们实现,我们需要根据具体的业务需求来实现该函数,在派生类中来实现step2函数:

class MyService : public Service{
int step2(){
// 具体业务逻辑的实现
}
}

派生类实现了step2函数后,通过调用run函数来运行程序:

void main (){
//...准备工作
Service * myService = new MyService();
if( myService->run()){
//...后续处理
}
// ...
}

我们的后台服务框架例子中,run函数定义了一个服务的稳定执行步骤,但某个步骤有着特定的需求无法马上定义,需要延迟到派生类中去实现,这时候就需要采用模板方法模式.模板方法模式要解决的问题是:如何在确定稳定操作结构的前提下,灵活地应对各个子步骤的变化或者晚期实现需求?

李建忠老师曾提过,重构获得设计模式(Refactoring to Patterns).设计模式的应用不宜先入为主,一上来就使用设计模式是对设计模式的最大误用,在敏捷软件开发中,提倡使用的是通过重构来获得设计模式,这也是最好的使用设计模式的方法.

而关于重构的关键技法,包括了:

  • 静态->动态
  • 早绑定->晚绑定
  • 继承->组合
  • 编译时依赖->运行时依赖
  • 紧耦合->松耦合

接下来我们来看看如何将一个程序,重构成模板方法模式.现代软件专业分工之后,也出现了"框架与应用程序的划分",框架实现人员先定义好框架的执行流程,也就是算法骨架(稳定),并提供可重写(overide)的接口(变化)给应用开发人员,以开发适应其应用程序的子步骤.模板方法通过晚绑定,实现了框架与应用程序之间的松耦合.

现在我们需要实现一个程序库,需要四个步骤来完成相应功能.其中step1,step3步骤稳定,而step2,step4则根据不同应用的具体需要,自行定义其具体功能,库开发人员无法预先实现好step2,step4.那么库开发人员可以先写好:


// 库开发人员
class Library{
public:
void step1(){
// 步骤1的具体实现
//...
}
void step3(){
//步骤3的具体实现
//...
}

应用程序开发人员则根据具体的应用需求,来实现剩余的两个步骤:


//应用程序开发人员
class Application{
public:
void step2(){
//步骤2的具体实现
//...
}
bool step4(){
//步骤4的具体实现
//...
}
}

然后应用程序开发人员还需要写一个main方法,将步骤以某种流程串起来:



//稳定
public static void main(String args[]) {
Library lib = new Library();
Application app = new Application();
lib.step1();
if (app.step2()) {
lib.step3();
}
app.step4();
}

这种办法实际上是一种C语言结构化的实现方式,虽然用的是C++,但没有体现出面向对象的特性来.main方法中,四个步骤的调用过程是相对稳定的,我们可以把这种稳定提升到库的实现中去,而应用程序开发人员,只需要实现"变化"的代码即可.这就引出了第二种做法.

第二种做法,是库开发人员不仅实现step1(),step3(),同时将step2(),step4()声明为纯虚函数,等待应用程序开发人员自己去实现这两个纯虚函数.注意到,main方法中定义的执行流程是相对稳定的,完全可以把这些步骤移动到库类中去.


//库开发人员
class Library{
public :
void run (){
step1();
if(step2()){ //支持变化-->虚函数的多态调用
step3();
}
step4(); //支持变化-->虚函数的多态调用
}
protected:
void step1(){ //稳定
//...
}
void step3(){ //稳定
//...
}
virtual bool step2() =0; //纯虚函数
virtual void step4() = 0; //纯虚函数
virtual ~Library(){
//...
}
};

注意step2,step4为纯虚函数,这是因为库开发人员无法知道怎么写,留给程序库开发人员来实现,也就是"把实现延迟",这在C++中体现为虚函数或纯虚函数,由应用程序开发人员继承Library以实现两个纯虚函数.这一段代码实际上体现了大部分设计模式应用的特点,也就是在稳定中包含着变化,run函数的算法流程是稳定的,但是算法的某个步骤是可变的,可变的延迟实现:


class Application: public Library{
protected:
virtual bool step2(){
//...子类重写实现
}
virtual void step4(){
//...子类重写实现
}
};

然后,应用程序开发人员,只需要通过多态指针来完成框架的使用:


public static void main(String args[]) {
Library * ptr = new Application();
ptr->run();
delete ptr;
}

指针ptr是一个多态指针,它声明为Library类型,实际指向的对象为Application类型对象.它会调用到基类的run方法,遇到step2,step4函数时,通过虚函数机制,调用到派生类实现的step2,step4函数.

回顾两种实现方式,我们可以发现,第一种实现方式中:

  • 库开发者负责step1,step3 ;
  • 应用程序开发者负责step2,step4,执行流程(稳定)

采用了模板方法模式的实现方式中:

  • 库开发者负责step1,step3,执行流程(稳定)
  • 应用程序开发者负责step2,step4

一般来说,框架/组件/库的实现,总是要先于应用程序的开发的.在第一种方式中,应用程序开发者(晚开发)的执行流程调用了库开发者定义好的函数(早开发),称为早绑定,而反过来在模板方法模式中,库开发者在执行流程中先调用了step2,step4函数,而这两个函数需要延迟到应用程序开发人员真正实现时,才通过虚函数机制进行调用,这种方式则称为早绑定.这便是重构使用设计模式的技法: 早绑定->晚绑定.

回过头来看看模板方法模式的定义:**定义一个操作中的算法的骨架,而将一些步骤延迟到子类中.Template Method使得子类刻意不改变一个算法的结构即可重定义该算法的某些特定步骤.(<<设计模式>> GoF) ** 所谓的骨架,要求是相对稳定的,在上面的例子中,如果step1,step3也是不稳定的,那么该情景下就不适用于适用设计模式,原因是软件体系中所有的东西都不稳定.设计模式的假设条件是必须有一个稳定点,如果没有稳定点,那么设计模式没有任何作用.反过来说,如果所有的步骤都是稳定的,这种极端情况也不适用于适用设计模式.设计模式总是处理"稳定中的变化"这种情景.设计模式最大的作用,是在稳定与变化之间寻找隔离点,然后来分离它们,从而来管理变化.从而我们也能够得到启发,学会分析出软件体系结构中哪部分是稳定的,哪部分是变化的,是学好设计模式的关键点.

再来看一看模板方法设计模式的结构:其中TemplateMethod() 方法也就是我们上面所说的run函数,它相对稳定,primitiveOperation1(),primitiveOperation2()为两个变化的函数,可由派生类实现,在TemplateMethod()中调用步骤.在下图中,红色圈为稳定的部分,而黑色圈为变化的部分.

在面向对象的时代,绝大多数的框架设计都使用了模板方法模式.作为一个应用程序开发人员,我们往往只需要实现几个步骤,框架便会把我们的步骤"串接"到执行流程中,有时候甚至连main函数都不用我们去实现.这样子也有弊端,我们看不见框架的执行流程,执行细节是怎么样的,往往有一种"只见树木不见森林"的感觉.

最后来总结以下模板方法设计模式.Template Method设计模式是一种非常基础性的设计模式,它要解决的问题是如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求.它使用了函数的多态性机制,为应用程序框架提供了灵活的拓展点,是代码复用方面的基本实现结构.Template Method设计模式明显划分了稳定与变化的关系,除了灵活应对子步骤的变化外,也是晚绑定的典型应用,通过反向控制结构,使得早期的代码可以调用晚期代码.而在具体实现上,被Template Method调用的虚函数,可以具有实现,也可以没有任何实现,这在C++中体现为虚函数或者纯虚函数,一般将这些函数设置为proteced方法.

C++设计模式:Template Method的更多相关文章

  1. 设计模式 Template Method模式 显示程序猿的一天

    转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/26276093 不断设计模式~ Template Method模式 老套路,看高清 ...

  2. 设计模式 - Template Method

    今天下午主要研究了设计模式中的Template Method(模版方法设计模式). 在Spring中,对各种O/RM进行了封装,比如对Hibernate有HibernateTemplate封装:对JD ...

  3. 设计模式Template Method模式(Template Method)摘录

    23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例.怎样创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化托付给还 ...

  4. 设计模式-模板方法设计模式--Template Method design pattern

    /** * Abstract implementation of the {@link org.springframework.context.ApplicationContext} * interf ...

  5. Android 设计模式Template Method模式

    自定义模板方法模式:定义的算法的骨架中的方法,虽然某些步骤推迟到子类中,下模板方法允许子类不能改变在的情况下,该算法的结构.算法重新定义某些步骤. 设计原则:不要给我们打电话.我会打电话给你.(像猎头 ...

  6. 设计模式-Template Method Pattern

    将generic部份放在abstract base class中的实现的方法中,而将和具体context相关的部份作为abstract base class的虚方法,由derivatives去实现. ...

  7. Caffe源码理解3:Layer基类与template method设计模式

    目录 写在前面 template method设计模式 Layer 基类 Layer成员变量 构造与析构 SetUp成员函数 前向传播与反向传播 其他成员函数 参考 博客:blog.shinelee. ...

  8. 设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习&quot;模板方法模式&quot;(Template Method Pattern)

    今天是五.四青年节,祝大家节日快乐.看着今天这标题就有食欲,夏天到了,醋溜土豆丝和清炒苦瓜适合夏天吃,好吃不上火.这两道菜大部分人都应该吃过,特别是醋溜土豆丝,作为“鲁菜”的代表作之一更是为大众所熟知 ...

  9. C#设计模式系列:模板方法模式(Template Method)

    你去银行取款的时候,银行会给你一张取款单,这张取款单就是一个模板,它把公共的内容提取到模板中,只留下部分让用户来填写.在软件系统中,将多个类的共有内容提取到一个模板中的思想便是模板方法模式的思想. 模 ...

随机推荐

  1. AccountName LoginName 变更

    当AD中把AccountName改掉后,网站集不会自动同步LoginName,需要使用命令行Move-SPUser domain/A->domian/B /*2013 Claim 认证  必须加 ...

  2. shell数组操作

    1.数组定义,shell使用一对括号表示数组,数组元素间用"空格"分隔 # 空数组arr1 arr1=() # 数组arr2,成员分别是1, 2, 3, 4, 5, 6 arr2= ...

  3. 获取UILabel宽度的方法

    - (CGFloat)labelLength:(NSString *)str font:(CGFloat)font{ str = ISSTRING(str) ? str : @"" ...

  4. Android开发之“点9”

    “点九”是andriod平台的应用软件开发里的一种特殊的图片形式,文件扩展名为:.9.png智能手机中有自动横屏的功能,同一幅界面会在随着手机(或平板电脑)中的方向传感器的参数不同而改变显示的方向,在 ...

  5. liunx环境下的mysql数据库配置文件my.conf内的参数含义

    [client]port = 3306socket = /tmp/mysql.sock [mysqld]port = 3306socket = /tmp/mysql.sock basedir = /u ...

  6. MySQL 没有索引 锁全表

    <h3 class="title" style="box-sizing: inherit; margin: 8px 0px 15px; padding: 0px; ...

  7. 利用Excel-Vba进行多表汇总和数据透视表

    汇总表格式 详情表格式 要求根据汇总表中的信息,到详情表中查找详细物料的具体个数 最终,对物料的个数进行汇总,结果如下图: ExcelVba代码如下(有一些注释代码供参考) Sub Start() S ...

  8. 20175234 2018-2019-2 《Java程序设计》第七周学习总结

    目录 20175234 2018-2019-2 <Java程序设计>第七周学习总结 教材学习内容总结 String类常用用法 Date类与Calendar类常用用法 Math类的常用方法 ...

  9. C# 很少人知道的科技

    本文来告诉大家在C#很少有人会发现的科技.即使是工作了好多年的老司机也不一定会知道,如果觉得我在骗你,那么请看看下面 因为C#在微软的帮助,已经从原来很简单的,到现在的很好用.在10多年,很少人知道微 ...

  10. SVN解决创建文件时不带锁

    解决创建文件时不带锁   C:\Documents and Settings\你的用户名\Application Data\Subversion   找到上面的用户路径 打开config添加 ### ...