C++设计模式:Template Method
我使用过一个简单的后台服务框架.这个框架上手很容易,我只需要继承一个基类,同时实现,或重写(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的更多相关文章
- 设计模式 Template Method模式 显示程序猿的一天
转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/26276093 不断设计模式~ Template Method模式 老套路,看高清 ...
- 设计模式 - Template Method
今天下午主要研究了设计模式中的Template Method(模版方法设计模式). 在Spring中,对各种O/RM进行了封装,比如对Hibernate有HibernateTemplate封装:对JD ...
- 设计模式Template Method模式(Template Method)摘录
23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例.怎样创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化托付给还 ...
- 设计模式-模板方法设计模式--Template Method design pattern
/** * Abstract implementation of the {@link org.springframework.context.ApplicationContext} * interf ...
- Android 设计模式Template Method模式
自定义模板方法模式:定义的算法的骨架中的方法,虽然某些步骤推迟到子类中,下模板方法允许子类不能改变在的情况下,该算法的结构.算法重新定义某些步骤. 设计原则:不要给我们打电话.我会打电话给你.(像猎头 ...
- 设计模式-Template Method Pattern
将generic部份放在abstract base class中的实现的方法中,而将和具体context相关的部份作为abstract base class的虚方法,由derivatives去实现. ...
- Caffe源码理解3:Layer基类与template method设计模式
目录 写在前面 template method设计模式 Layer 基类 Layer成员变量 构造与析构 SetUp成员函数 前向传播与反向传播 其他成员函数 参考 博客:blog.shinelee. ...
- 设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)
今天是五.四青年节,祝大家节日快乐.看着今天这标题就有食欲,夏天到了,醋溜土豆丝和清炒苦瓜适合夏天吃,好吃不上火.这两道菜大部分人都应该吃过,特别是醋溜土豆丝,作为“鲁菜”的代表作之一更是为大众所熟知 ...
- C#设计模式系列:模板方法模式(Template Method)
你去银行取款的时候,银行会给你一张取款单,这张取款单就是一个模板,它把公共的内容提取到模板中,只留下部分让用户来填写.在软件系统中,将多个类的共有内容提取到一个模板中的思想便是模板方法模式的思想. 模 ...
随机推荐
- 选择移动web开发框架研究——有mui、frozenui以及Sencha Touch等
纯粹的总结一下移动web开发框架,移动 web开发框架有jQuery Mobile .Sencha Touch等等,他们都来源于web开发,是成熟的框架,jQuery Mobile出自于jQuery家 ...
- Javascript数据类型共有六种
Javascript数据类型共有六种 /* var box; alert(typeof box); // box是Undefined类型,值是undefined,类型返回的字符串是undefined ...
- CSS页面渲染优化属性will-change
前面的话 当我们通过某些行为(点击.移动或滚动)触发页面进行大面积绘制的时候,浏览器往往是没有准备的,只能被动使用CPU去计算与重绘,由于没有事先准备,应付渲染够呛,于是掉帧卡顿.而CSS属性wi ...
- JS消化理解
JS执行的时候是必须在网页里面执行,和样式表差不多,也是内嵌的样式表,嵌在网页里面或外部的! 一 嵌在网页里面怎么嵌? 如果你想在网页里面嵌脚本,你需要在网页里面打出一块区域,这块区域来写脚本,在写样 ...
- 解决Ubuntu 16.04 软件中心闪退
就是上面这个Ubuntu软件中心,类似如应用市场,今天不知怎么回事竟然抽风了,打开之后几秒就闪退了,导致我安装sublime一致失败,百度之后才知道这是16.04版本的一个毛病,按照我的性格,手机软件 ...
- iOS核心笔记—源代码管理工具-SVN
源代码管理工具-SVN 一. 源代码管理工具概述 1. 源代码管理工具的作用? > 能追踪一个项目从诞生一直到定案的过程 > 记录一个项目的所有内容变化,无限制返回 > 查看特定版本 ...
- js中的innerHTML和outerHTML区别
一.区别:1)innerHTML: 从对象的起始位置到终止位置的全部内容,不包括Html标签.2)outerHTML: 除了包含innerHTML的全部内容外, 还包含对象标签本身. 二.例子: &l ...
- java中函数是值传递还是引用传递?
相信有些同学跟我一样,曾经对这个问题很疑惑.在网上也看了一些别人说的观点,评论不一.有说有值传递和引用传递两种,也有说只有值传递的,这里只说下个人见解 先看一个例子 public class Test ...
- MySQL管理命令
1.验证MySQL安装 在成功安装Mysql后,一些基础表会表初始化,在服务器启动后,你可以通过简单的测试来验证Mysql是否工作正常. 使用 mysqladmin 工具来获取服务器状态: 使用 my ...
- 【翻译】理解Joomla!模板
最近在摸索Joomla的模板开发,看文档的时候心血来潮就干脆把这篇翻译过来,第一次翻译技术文档,肯定有很多错误,希望大家多多批评指正. 原文地址:https://docs.joomla.org/Und ...