[设计模式] 22 模板方法模式 template
转http://www.jellythink.com/archives/407
在GOF的《设计模式:可复用面向对象软件的基础》一书中对模板方法模式是这样说的:定义一个操作中的算法骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的接口即可重定义改算法的某些特定步骤。
我结合我在实际开发项目中的一个例子来说说这个模板方法模式吧。我们曾经做过一款产品,这个产品类似于一个云端的文件管理客户端。对于这样的一个客户端,由于其云端的服务器有三种,而每一种服务器之间的通信方式和对外公开的接口都是不是一致的,这就需要实现的客户端要屏蔽云端服务器和接口的差异性,而提供统一的操作界面,所以在实现这个客户端的同时,我们实现了一个框架,一个对于服务器和接口是通用的框架,比如就拿文件下载来说说。我们的实现大概如下:
class FileOperation { public: bool DownloadFile(wchar_t *pSrc, wchar_t *pDest) { if (!pSrc || !pDest) return false; if (!DoBeginDownloadFile(pSrc, pDest)) return false; if (!DoDownloadFile(pSrc, pDest)) return false; if (!DoEndDownloadFile(pSrc, pDest)) return false; } protected: virtual bool DoBeginDownloadFile(wchar_t *pSrc, wchar_t *pDest); virtual bool DoDownloadFile(wchar_t *pSrc, wchar_t *pDest); virtual bool DoEndDownloadFile(wchar_t *pSrc, wchar_t *pDest); }; class HttpFileOperation : public FileOperation { protected: virtual bool DoBeginDownloadFile(wchar_t *pSrc, wchar_t *pDest); virtual bool DoDownloadFile(wchar_t *pSrc, wchar_t *pDest); virtual bool DoEndDownloadFile(wchar_t *pSrc, wchar_t *pDest); }; class SOAPFileOperation : public FileOperation { protected: virtual bool DoBeginDownloadFile(wchar_t *pSrc, wchar_t *pDest); virtual bool DoDownloadFile(wchar_t *pSrc, wchar_t *pDest); virtual bool DoEndDownloadFile(wchar_t *pSrc, wchar_t *pDest); };
下载文件的流程为:先调用DoBeginDownloadFile,执行下载文件之前的一些操作,再调用DoDownloadFile实现真正的文件下载,最后调用DoEndDownloadFile完成文件下载的清理工作。对于任何服务器,下载文件的这个流程是不会发生变化的。而在DoBeginDownloadFile、DoDownloadFile和DoEndDownloadFile的内部具体是如何实现的,由程序员根据具体的云端服务器和对外公开的接口来完成的。最终客户端去完成文件下载操作时,只会调用DownloadFile函数就可以完成。可以看到,在上面的代码中,只有DownloadFile是public的,其它的操作函数都是protected。这也意味着,我们完成的框架对外只公开DownloadFile接口。
AbstractClass(抽象类):定义抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤。主要是实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作。
ConcreteClass(具体类):实现原语操作以完成算法中与特定子类相关的步骤。
由于在具体的子类ConcreteClass中重定义了实现一个算法的各步骤,而对于不变的算法流程则在AbstractClass的TemplateMethod中完成。
使用场合
模板方法是一种代码复用的基本技术。它们在类库中尤为重要,它们提取了类库中的公共行为。在使用模板方法时,很重要的一点是模板方法应该指明哪些操作是可以被重定义的,以及哪些是必须被重定义的。要有效的重用一个抽象类,子类编写者必须明确了解哪些操作是设计为有待重定义的。
代码实现
这里就根据上面的类图,对模板方法模式进行了简单的实现。由于该模式非常简单,所以也没有更多的可以讲的了。
#include <iostream> using namespace std; class AbstractClass { public: void TemplateMethod() { PrimitiveOperation1(); cout<<"TemplateMethod"<<endl; PrimitiveOperation2(); } protected: virtual void PrimitiveOperation1() { cout<<"Default Operation1"<<endl; } virtual void PrimitiveOperation2() { cout<<"Default Operation2"<<endl; } }; class ConcreteClassA : public AbstractClass { protected: virtual void PrimitiveOperation1() { cout<<"ConcreteA Operation1"<<endl; } virtual void PrimitiveOperation2() { cout<<"ConcreteA Operation2"<<endl; } }; class ConcreteClassB : public AbstractClass { protected: virtual void PrimitiveOperation1() { cout<<"ConcreteB Operation1"<<endl; } virtual void PrimitiveOperation2() { cout<<"ConcreteB Operation2"<<endl; } }; int main() { AbstractClass *pAbstractA = new ConcreteClassA; pAbstractA->TemplateMethod(); AbstractClass *pAbstractB = new ConcreteClassB; pAbstractB->TemplateMethod(); if (pAbstractA) delete pAbstractA; if (pAbstractB) delete pAbstractB; }
最近有个招聘会,可以带上简历去应聘了。但是,其中有一家公司不接受简历,而是给应聘者发了一张简历表,上面有基本信息、教育背景、工作经历等栏,让应聘者按照要求填写完整。每个人拿到这份表格后,就开始填写。如果用程序实现这个过程,该如何做呢?一种方案就是用模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。我们的例子中,操作就是填写简历这一过程,我们可以在父类中定义操作的算法骨架,而具体的实现由子类完成。下面给出它的UML图。
其中FillResume() 定义了操作的骨架,依次调用子类实现的函数。相当于每个人填写简历的实际过程。接着给出相应的C++代码。
#include <iostream> #include <string.h> using namespace std; class Resume { protected: virtual void SetPersonalInfo() {} virtual void SetEducation() {} virtual void SetWorkExp() {} public: void FillResume() { SetPersonalInfo(); SetEducation(); SetWorkExp(); } }; class ResumeA: public Resume { protected: void SetPersonalInfo() { cout<<"A's PersonalInfo"<<endl; } void SetEducation() { cout<<"A's Education"<<endl; } void SetWorkExp() { cout<<"A's Work Experience"<<endl; } }; class ResumeB: public Resume { protected: void SetPersonalInfo() { cout<<"B's PersonalInfo"<<endl; } void SetEducation() { cout<<"B's Education"<<endl; } void SetWorkExp() { cout<<"B's Work Experience"<<endl; } }; int main() { Resume *r1; r1 = new ResumeA(); r1->FillResume(); delete r1; r1 = new ResumeB(); r1->FillResume(); delete r1; r1 = NULL; return 0; }