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)
你去银行取款的时候,银行会给你一张取款单,这张取款单就是一个模板,它把公共的内容提取到模板中,只留下部分让用户来填写.在软件系统中,将多个类的共有内容提取到一个模板中的思想便是模板方法模式的思想. 模 ...
随机推荐
- Shell 基础
1.结构 #!指定执行脚本的shell #!/bin/sh # 注释行 命令和控制结构 2.修改权限 chmod +x ... 3 ...
- animate的{queue:false,duration:400}意思
示例:$(document).ready(function(){ $('.tmplS').hover(function(){ $(".cover", this).stop().an ...
- NetStream.appendBytes, 走向Flash P2P VOD的第一步
之前被告知可以自行实现Flash p2p的点播功能, 但一直疑惑, 印象中NetStream并未提供相关方法, 前天看订阅时发现的文章: ByteArray Access to NetStream i ...
- javaScript基础详解(1)
javaScript基础详解 首先讲javaScript的摆放位置:<script> 与 </script> 可以放在head和body之间,也可以body中或者head中 J ...
- web前端性能调优(二)
项目经过第一波优化之后APP端已基本已经符合我们的要求了,但是TV端还是反应比较慢,页面加载和渲染都比较慢了一点,我觉的还是有必要在进行一些优化,经过前面的优化,我们的优化空间已经小了一部分,不过还是 ...
- POJ1556(割点)
SPF Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 8114 Accepted: 3716 Description C ...
- easyui帮助文档地址
http://www.jeasyui.com/documentation/index.php# http://www.jeasyui.net/tutorial/22.html http://www.j ...
- 每日一水之strcmp用法
strcmp函数 C/C++函数,比较两个字符串 设这两个字符串为str1,str2, 若str1==str2,则返回零: 若str1<str2,则返回负数: 若str1>str2,则返回 ...
- PHP文本处理之中文汉字字符串转换为数组
在PHP中我们可以通过str_split 将字符串转换为数组,但是却对中文无效,下面记录一下个人将中文字符串转换为数组的方法. 用到的PHP函数 mb_strlen - 获取字符串的长度 mb_sub ...
- PowerShell 批量修改AD属性
环境:win 2008 R2 在管理工具中打开用于 windows powershell 的ActiveDirectory模块命令行窗口或打开命令提示符窗口输入PowerShell回车再输入impor ...