属于类别
- 组件协作模式
引言
结构化软件设计流程
在工程项目中,我们经常和别人一起开发完成一个任务。这个任务可能分成好几个步骤,我们按步骤进行分工。
(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)程序开发人员(写框架的人)```cppclass Library{ //程序库开发人员public://稳定 template methodvoid 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,只暴露给子类即可以




