模式动机(Template Method Pattern):所谓模板,就是具有通用性的一个框架,在不同的具体环境下可以匹配不同的行为。在程序设计中,代码的复用一直是人们追求的目标,更好地利用已有的设计,不仅可以极大地提高开发效率,而且也能保证软件本身的鲁棒性。
一种代码复用的基本原则就是继承机制,但是我们也知道,普通的继承可能会造成派生类比较臃肿,具有“强制性”实现的特性,特别是基类为抽象接口时更是如此。如何让基类承担更多的一般性职责,这就是模板方法模式所解决的问题了。
在模板方法中,将算法的框架设计放在基类中定义,即基类中定义一些通用逻辑及其实现,而将可变逻辑部分留出接口等待派生类完成,这样既实现了代码的复用,又将部分具体实现权利下放到了派生类。通过模板方法,派生类可以在不更改基类中算法框架的前提下即可定制自己的特殊性。
在模板方法的实现中,有如下参与者:
抽象基类(AbstractClass):基类中定义有模板方法TemplateMethod()和基本操作PrimitiveOperation[1...n]()。模板方法调用基本操作来显示功能,基本操作具体包括如下类型:抽象操作(Abstract Operation)、具体操作(Concrete Operation)、工厂方法(Factory Method)、钩子操作(Hook Operation)。
抽象操作:也称为原语操作,即只能被模板方法调用的操作。这种操作适合定义为protected类型,这样可以被派生类重新定义特定行为。实际中尽量减少原语操作以减轻派生 类的负担。
具体操作:在基类中定义并实现的功能,这种操作有两种作用:其一是服务于基类中的相互调用及其语义的实现;其二是作为该类型的一般性操作被派生类复用。
工厂方法:用于产生该类型对象中要使用的其他类型对象的一种措施,属于设计模式间的结合。在系统不是特别复杂时,该方法可以用其他方法替换。
钩子操作:在设计类继承关系时,如果继承层次较多,我们可能会忽略对基类中的某些行为的继承,进而在派生类中又给出自己的完整实现,这样就可能会设计出比较松散的代 码结构。可以用如下设计改善:将基类中的这种操作定义为一个模板方法,在模板方法中调用一个钩子操作,而这个钩子操作最好实现为一个空操作。利用派生类重定义的钩子 行为来实现基类的模板方法,即通过派生类的具体实现来对基类进行反向控制,这也是“钩子”这个名称的来历。
具体派生类(ConcreteClass):实现基类中的特定原语操作及其钩子操作。
模式结构图:
模式代码:
bt_模板方法模式.h:
1 #ifndef TMP_H 2 #define TMP_H 3 #include <iostream> 4 using namespace std; 5 6 /* 7 抽象基类 8 */ 9 class AbstractClass 10 { 11 public: 12 virtual ~AbstractClass(){ } 13 void TemplateMethod() // 模板方法 14 { 15 cout << "AbstractClass::TemplateMethod()" << endl; 16 cout << "{" << endl; 17 PrimitiveOperation1(); 18 PrimitiveOperation2(); 19 cout << "}" << endl; 20 } 21 void ConcreteOperation() // 具体操作 22 { 23 cout << "AbstractClass::ConcreteOperation()" << endl; 24 } 25 protected: 26 virtual void PrimitiveOperation1(); // 原语操作 27 virtual void PrimitiveOperation2() = 0; // 原语操作 28 virtual bool HookOperation() = 0; // 钩子操作 29 }; 30 void AbstractClass::PrimitiveOperation1() 31 { 32 cout << "AbstractClass::PrimitiveOperation1()" << endl; 33 if(HookOperation()) 34 ConcreteOperation(); 35 else 36 cout << "ConcreteClass::HookOperation() returns FALSE" << endl; 37 } 38 39 /* 40 具体派生类 41 */ 42 class ConcreteClass : public AbstractClass 43 { 44 protected: 45 virtual void PrimitiveOperation2() 46 { 47 cout << "ConcreteClass Implements AbstractClass::PrimitiveOperation2()" << endl; 48 } 49 virtual bool HookOperation() 50 { 51 cout << "ConcreteClass Redefines AbstractClass::HookOperation()" << endl; 52 return true; 53 } 54 }; 55 56 #endif // TMP_H
测试用例.cpp:
1 #include "bt_模板方法模式.h" 2 3 int main() 4 { 5 AbstractClass* base = new ConcreteClass; 6 base->TemplateMethod(); 7 delete base; 8 9 return 0; 10 }
如果派生类中的钩子函数返回false,那么基类中的原语操作会受到影响,这种通过派生类来反向控制基类的方式也被称为“Hollywood Principle,that is: Don’t call me,I’ll call you”,如下所示:
1 virtual bool HookOperation() 2 { 3 cout << "ConcreteClass Redefines AbstractClass::HookOperation()" << endl; 4 return false; 5 }
模式总结:
::模板方法模式在类库的设计中有着不可替代的作用,其实我们自己设计的抽象类中也经常会用到,比如抽象类中的接口合作等。
::模板方法的优势在于派生类不需要改变基类的算法框架即可重定义自己的特定部分,较之于一般的继承而言,代码复用率很高【因为一般的继承关系会覆盖基类的整个方法】。
::模板方法中钩子函数的作用很大,通过派生类特性反向控制基类的行为,实现程序结构的灵活控制。在使用模板方法模式时,限制类型最好遵循以下原则:
::在抽象基类中,不需要派生类重新设计的方法,定义为非虚方法(non-virtual);可能需要派生类重新设计的方法,定义为虚方法(virtual),并给出默认实现;必须由派生类重新 设计的方法,定义为纯虚方法(pure-virtual);当然,这些只是一般性的约定,特殊设计需要根据需求而定。
::模板方法模式使用继承来改变算法的一部分,而策略模式使用委托来改变整个算法。