属于类别

  1. 组件协作模式

    引言

    结构化软件设计流程

    在工程项目中,我们经常和别人一起开发完成一个任务。这个任务可能分成好几个步骤,我们按步骤进行分工。
    (1)程序库开发人员
    1. class Library{
    2. public:
    3. void Step1(){ //第一步
    4. //...
    5. }
    6. void Step3(){ //第三步
    7. //...
    8. }
    9. void Step5(){ //第五步
    10. //...
    11. }
    12. };
    (2)应用程序开发人员(调用别人写好的库) ```cpp class Application{ //应用程序开发人员 public: bool Step2(){ //第二步
    1. //...
    } void Step4(){ //第四步
    1. //...
    } };

int main() { Application app(); Library lib();

  1. //第一步(库提供)
  2. lib.Step1();
  3. //第二步(自己写的函数)
  4. if (app.Step2()){
  5. //第三步(库提供)
  6. lib.Step3();
  7. }
  8. //第四步(自己写的)
  9. for (int i = 0; i < 4; i++){
  10. app.Step4();
  11. }
  12. //第五步(库提供)
  13. lib.Step5();

}

  1. <a name="m6zLC"></a>
  2. ### 面向对象软件设计流程
  3. (1)程序开发人员(写框架的人)
  4. ```cpp
  5. class Library{ //程序库开发人员
  6. public:
  7. //稳定 template method
  8. void Run(){
  9. Step1();
  10. if (Step2()) { //支持变化 ==> 虚函数的多态调用
  11. Step3();
  12. }
  13. for (int i = 0; i < 4; i++){
  14. Step4(); //支持变化 ==> 虚函数的多态调用
  15. }
  16. Step5();
  17. }
  18. //基类的虚构函数需要写成虚的
  19. //如果用父类指针去delete的时候,它可能不会调用到子类的析构函数
  20. //有可能会出错
  21. virtual ~Library(){ }
  22. protected:
  23. void Step1() { //稳定
  24. //.....
  25. }
  26. void Step3() { //稳定
  27. //.....
  28. }
  29. void Step5() { //稳定
  30. //.....
  31. }
  32. //写库的人不确定Step2、Step4具体要做什么,等到使用的人自己写
  33. virtual bool Step2() = 0;//变化
  34. virtual void Step4() =0; //变化
  35. };

(2)应用程序开发人员(调用别人写好的库)

  1. class Application : public Library {//应用程序开发人员
  2. protected:
  3. //应用程序开发人员确定这两个要实现什么,再重写
  4. virtual bool Step2(){
  5. //... 子类重写实现
  6. }
  7. virtual void Step4() {
  8. //... 子类重写实现
  9. }
  10. };
  11. int main() {
  12. //这里定义的是一个多态指针,声明类型是Library,实际类型是Application
  13. //所以当它调用虚函数的时候,它就会按照虚函数的动态绑定规则来调用
  14. Library* pLib=new Application();
  15. //run()本身不是虚函数,是父类Library的成员函数
  16. //但run()内部会调用到虚函数,所以它会根据虚函数调用规则去找子类中的具体实现
  17. //如这里Step1、Step3、Step5会调用到父类Library中的函数
  18. //而Step2、Step4会调用到Application中的
  19. lib->Run();
  20. //用父类指针去delete,它可能不会调用到子类的析构函数,因此可能会出错
  21. delete pLib;
  22. }

总结对比

对比 结构化 面向对象

1. Library库程序比Application写的早;
1. 在写库程序时候,任务执行顺序(程序主流程)第一步、第三步、第五步就已经明确了
1. 第二步、第四步要在使用阶段(Application)才能确定,需要使用者自定义实现
流程 应用开发人员做简答题
image.png
应用开发人员做做填空题
image.png
绑定时间 在Application中写程序主流程,调用Library写好的第一步、第三步、第五步
image.png
在Library中写程序主流程,调用Application中的第二步、第四步
image.png
意义 应用程序开发人员的负担比较重,而且也要查看文档,主流程应该要怎么写。

动机

在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。

如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?

模式定义

定义一个操作中算法的骨架(稳定,确定的东西),而将一些步骤(变化,不确定的东西)延迟到子类中。Template Method使得子类可以不改变(即复用)一个算法的结构,即可重定义(override重写)该算法的某些特定步骤。——《设计模式GOF》

  1. 算法的骨架:在编写库程序过程中,稳定的,确定的东西;如例子中,执行的主流程、一些确定的步骤(Step1、Step3、Step5)
  2. 延迟到子类的步骤:在编程应用程序过程中才能确定的东西,即变化,不确定的东西。如例子中,Step2、Step4,在子类中可以重写它
  3. Template Method模板方法:即上面提到的算法骨架,例子中的run()

在C++语言中:

  1. 稳定的代码写成非虚函数
  2. 非稳定的代码写成虚函数

【不适用场景】
如果执行的主流程(即例子中的Run())是不稳定,在编写库程序时不能确定的,是变化的。那“模板方法”设计模式就不适用于此类情况。
即算法的骨架必须是稳定,确定不会改变的。

【缺点】
程序主流程在Library中,Application开发人员很容易有一种“只见树木,不见森林”的感觉。

结构

  1. 红色是稳定的部分
  2. 蓝色的是变化的部分

image.png

要点总结

  1. Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
  2. 除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你”反向控制结构是Template Method的典型应用。
  3. 在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法。
    1. 子流程Step2、Step4作为Public出去是没有意义的,只有在主流程run()调用下,才有意义。
    2. 所以建议设置为protected,只暴露给子类即可以