属于类别
- 组件协作模式
引言
结构化软件设计流程
在工程项目中,我们经常和别人一起开发完成一个任务。这个任务可能分成好几个步骤,我们按步骤进行分工。
(1)程序库开发人员
(2)应用程序开发人员(调用别人写好的库) ```cpp class Application{ //应用程序开发人员 public: bool Step2(){ //第二步class Library{
public:
void Step1(){ //第一步
//...
}
void Step3(){ //第三步
//...
}
void Step5(){ //第五步
//...
}
};
} void Step4(){ //第四步//...
} };//...
int main() { Application app(); Library lib();
//第一步(库提供)
lib.Step1();
//第二步(自己写的函数)
if (app.Step2()){
//第三步(库提供)
lib.Step3();
}
//第四步(自己写的)
for (int i = 0; i < 4; i++){
app.Step4();
}
//第五步(库提供)
lib.Step5();
}
<a name="m6zLC"></a>
### 面向对象软件设计流程
(1)程序开发人员(写框架的人)
```cpp
class Library{ //程序库开发人员
public:
//稳定 template method
void Run(){
Step1();
if (Step2()) { //支持变化 ==> 虚函数的多态调用
Step3();
}
for (int i = 0; i < 4; i++){
Step4(); //支持变化 ==> 虚函数的多态调用
}
Step5();
}
//基类的虚构函数需要写成虚的
//如果用父类指针去delete的时候,它可能不会调用到子类的析构函数
//有可能会出错
virtual ~Library(){ }
protected:
void Step1() { //稳定
//.....
}
void Step3() { //稳定
//.....
}
void Step5() { //稳定
//.....
}
//写库的人不确定Step2、Step4具体要做什么,等到使用的人自己写
virtual bool Step2() = 0;//变化
virtual void Step4() =0; //变化
};
(2)应用程序开发人员(调用别人写好的库)
class Application : public Library {//应用程序开发人员
protected:
//应用程序开发人员确定这两个要实现什么,再重写
virtual bool Step2(){
//... 子类重写实现
}
virtual void Step4() {
//... 子类重写实现
}
};
int main() {
//这里定义的是一个多态指针,声明类型是Library,实际类型是Application
//所以当它调用虚函数的时候,它就会按照虚函数的动态绑定规则来调用
Library* pLib=new Application();
//run()本身不是虚函数,是父类Library的成员函数
//但run()内部会调用到虚函数,所以它会根据虚函数调用规则去找子类中的具体实现
//如这里Step1、Step3、Step5会调用到父类Library中的函数
//而Step2、Step4会调用到Application中的
lib->Run();
//用父类指针去delete,它可能不会调用到子类的析构函数,因此可能会出错
delete pLib;
}
总结对比
对比 | 结构化 | 面向对象 |
---|---|---|
1. Library库程序比Application写的早; 1. 在写库程序时候,任务执行顺序(程序主流程)和第一步、第三步、第五步就已经明确了 1. 第二步、第四步要在使用阶段(Application)才能确定,需要使用者自定义实现 |
||
流程 | 应用开发人员做简答题 |
应用开发人员做做填空题 |
绑定时间 | 在Application中写程序主流程,调用Library写好的第一步、第三步、第五步 |
在Library中写程序主流程,调用Application中的第二步、第四步 |
意义 | 应用程序开发人员的负担比较重,而且也要查看文档,主流程应该要怎么写。 |
动机
在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
模式定义
定义一个操作中算法的骨架(稳定,确定的东西),而将一些步骤(变化,不确定的东西)延迟到子类中。Template Method使得子类可以不改变(即复用)一个算法的结构,即可重定义(override重写)该算法的某些特定步骤。——《设计模式GOF》
- 算法的骨架:在编写库程序过程中,稳定的,确定的东西;如例子中,执行的主流程、一些确定的步骤(Step1、Step3、Step5)
- 延迟到子类的步骤:在编程应用程序过程中才能确定的东西,即变化,不确定的东西。如例子中,Step2、Step4,在子类中可以重写它
- Template Method模板方法:即上面提到的算法骨架,例子中的run()
在C++语言中:
- 稳定的代码写成非虚函数
- 非稳定的代码写成虚函数
【不适用场景】
如果执行的主流程(即例子中的Run()
)是不稳定,在编写库程序时不能确定的,是变化的。那“模板方法”设计模式就不适用于此类情况。
即算法的骨架必须是稳定,确定不会改变的。
【缺点】
程序主流程在Library中,Application开发人员很容易有一种“只见树木,不见森林”的感觉。
结构
- 红色是稳定的部分
- 蓝色的是变化的部分
要点总结
- Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
- 除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你”的反向控制结构是Template Method的典型应用。
- 在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法。
- 子流程Step2、Step4作为Public出去是没有意义的,只有在主流程run()调用下,才有意义。
- 所以建议设置为protected,只暴露给子类即可以