设计模式---组件协作模式之模板方法模式(Tempalte Method)
前提:组件协作模式
现代软件专业分工之后的第一个结构是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常见的模式。
我们常常使用框架来写自己的应用,这样一个划分,必然中间需要有大量的协作问题。
典型模式(体现最为强烈,特征表现最为明显)
模板方法模式(Tempalte Method)
策略模式(Strategy)
观察者模式(Observer/Event)
一:模板方法模式(Tempalte Method)
(一)动机
在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现(必有一个早一个晚)。
如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
(二)原代码讲解(模拟流程)
1.程序库开发(框架开发)人员,其中实现了1,3,5步骤
//程序库开发人员 class Library{ public: void Step1(){ //... } void Step3(){ //... } void Step5(){ //... } };
2.应用开发人员,我们需要实现2,4步骤
//应用程序开发人员 class Application{ public: bool Step2(){ //... } void Step4(){ //... } };
然后我们还需要具体的把这几个步骤以某种流程将其串起来
int main() { Library lib(); Application app(); lib.Step1(); if (app.Step2()){ lib.Step3(); } for (int i = 0; i < 4; i++){ app.Step4(); } lib.Step5(); }
3.总结
应用开发人员实现了部分应用程序步骤开发和程序的整体流程。
而这个流程既要调用到类库开发人员写的方法step1,step3,step5,同时还需要调用到应用程序开发人员(自己或协作者)写的step2,step4方法。
整体流程应该由框架设计人员为我们设定好,常常不需要我们更改,而且是稳定的
(三)改进版代码讲解(了解调用机制)
1.框架开发人员,直接为我们规定流程
//程序库开发人员 class Library{ public: //稳定 template method void Run(){ Step1(); if (Step2()) { //支持变化 ==> 虚函数的多态调用 Step3(); } for (int i = 0; i < 4; i++){ Step4(); //支持变化 ==> 虚函数的多态调用 } Step5(); } virtual ~Library(){ } protected: void Step1() { //稳定 //..... } void Step3() {//稳定 //..... } void Step5() { //稳定 //..... } virtual bool Step2() = 0;//变化 virtual void Step4() =0; //变化 };
由框架开发人员来为我们写出流程
2.应用开发人员,只需要实现步骤,和调用框架运行
//应用程序开发人员 class Application : public Library { protected: virtual bool Step2(){ //... 子类重写实现 } virtual void Step4() { //... 子类重写实现 } }; int main() { Library* pLib=new Application(); //多态指针 lib->Run(); delete pLib; //注意上面,我们是使用的虚析构函数,是有必要的,若是不写成虚析构函数,我们就调用不了子类的析构函数,有可能出错,所以任何基类我们都应该写出析构函数,而且是虚析构函数 } }
(四)两种形式比较
1.第一种是结构化软件设计流程
2.第二种是面向对象软件设计流程
(五)调用关系
1.第一种调用关系是:早绑定
2.第二种调用关系是:晚绑定
(六)早绑定和晚绑定
定义:
Library出现早,Application出现晚,晚的东西去调用早的东西就叫做早绑定,像C语言这类就是如此
Application出现晚,我们使用早的Library去调用早的东西就叫做晚绑定,面向对象语言都实现这种
或者:
当我们写完Application就实现了Run方法去调用library,所以很早就调用了,就是早绑定
而当我们使用第二种方法,我们实现了Application后,并不能直接运行,而是要有library去将两者绑定在一起,所以是晚绑定
(七)模式定义
定义一个操作中的算法的骨架<Run>(稳定),而将一些步骤延迟<定义一个虚函数,让子类去实现(重写)这个虚函数>(变化)到子类<支持子类来变化,我现在定不下来如何实现,让子类来实现>中。
Template Method使得子类可以不改变(复用)一个算法的结构即可重定义(override重写)该算法的某些特定步骤。
——《设计模式》GOF
1.第一种是结构化思想实现,Application实现较复杂
2.第二种是我们的模板方法模式,是改良后的,模板就是样板,我们的Run方法就是一个样板
复用Run方法
//稳定 template method void Run(){ Step1(); if (Step2()) { //支持变化 ==> 虚函数的多态调用 Step3(); } for (int i = 0; i < 4; i++){ Step4(); //支持变化 ==> 虚函数的多态调用 } Step5(); }
稳定中有变化(相对的)
Run方法就是稳定的,其中的虚函数step2,step4就是变化的。稳定的代码,我们要写成非虚函数,而支持变化的我们要写成虚函数
(八)在模式应用的时候:核心就是去分辨出来软件体系中哪些是稳定的哪些是变化的
相对稳定和相对变化,不讨论绝对,不然设计模式全不适用
(九)缺点
在我们使用C编写windows程序时,需要我们自己去写出流程,虽然麻烦但是了解更多。
但是对于面向对象模板方法模式的使用,框架是将流程隐藏在了library中,我们若是只实现了application,那么我们只是完成业务,而未了解流程,只见树木不见森林
显然,流程是一个核心,我们应该去了解library做了什么
随着我们能力提升,我们需要去实现library
(十)类图(角色和职责)
我们通过类图知道是在稳定点中去调用了变化点,其中并未写出step1,step2,step5等稳定点
我们在学习设计模式的时候最好通过类图去了解哪些是稳定的哪些是变化的,学会去画出类图
(十一)要点总结
1.Template Mechod模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的拓展点,是代码复用方面的基本实现结构。 2.除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你<晚绑定>”的反向控制结构是Template Method的典型应用。 3.在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),但一般推荐将它们设置为protected方法<单独作为一个方法暴露出去给人家调用时没有意义的,他往往放在一个流程中,才有意义>。
(十二)应用场景
一般应用在具有以下条件的应用中:
1.具有统一的操作步骤或操作过程 2.具有不同的操作细节 3.存在多个具有同样操作步骤的有场景,但某些具体的操作细节却各不相同
总结
在抽象类中统一操作步骤,并规定好接口,让子类实现接口,这样可以把各个具体子类和操作步骤解耦合
(十三)案例实现
#include <iostream> using namespace std; class MakeCar { public: void make() { makeHead(); makeBody(); makeTail(); } virtual ~MakeCar(){} protected: virtual void makeHead() = 0; virtual void makeBody() = 0; virtual void makeTail() = 0; };
class MakeBus :public MakeCar { public: virtual void makeHead() { cout << "Bus install head" << endl; } virtual void makeBody() { cout << "Bus install body" << endl; } virtual void makeTail() { cout << "Bus install tail" << endl; } };
class MakeJeep :public MakeCar { public: virtual void makeHead() { cout << "Jeep install head" << endl; } virtual void makeBody() { cout << "Jeep install body" << endl; } virtual void makeTail() { cout << "Jeep install tail" << endl; } };
void main() { MakeCar* bus = new MakeBus(); bus->make(); delete bus; MakeCar* jeep = new MakeJeep(); jeep->make(); delete jeep; cout << "test make car finished" << endl; system("pause"); return; }