当在编写软件的过程中,对于某一个功能的实现,常有固定的操作结构及步骤,但对于不同需求细节而言,我们经常希望某些子步骤能够作出特有的改变,例如使用某些第三方库的功能时,我们希望能够在函数调用的过程中添加一些我们自己的需求。
在这种稳定的框架中,那么如何灵活应对各个子步骤的变化,或者后期的需求改变呢?这里我们就抛出本篇的设计模式:模版方法模式
先抛出定义:定义一个操作中的算法骨架**(稳定),而将算法的一些步骤延迟(变化)到子类中,使得子类可以不改变(复用)该算法结构的情况下重定义(override)**该算法的某些特定步骤。它是一种类行为型模式。
光通过定义是晦涩难懂的,下面我们通过具体的例子来理解:
假设我们使用如下的类来举例,以Library类代表第三方库,以Application代表我实际开发的需求, 如果在这样一个固定的框架中,我需要通过Lib的流程来策略选择具体实现的操作,那么他们之间的关系流程如下:
class Library{
public:
bool step1(){
//...
}
int step3(){
//...
}
void step5(){
//...
}
};
class Application{
public:
void step2(){
//...
}
void step4(){
//...
}
};
int main(){
Library* lib=new Library();
Application* app=new Application();
if(lib->step1())
app->step2();
for(int i=0,n=lib->step3();i<n;++i)
app->step4();
app->step5();
//delete...
}
简单来讲,上述的流程便是由程序库设定稳定部分,并由开发人员写出变化部分和程序主流程,这便是一种早绑定的实现方式,
什么是早绑定?
在上述例子里,Library一定是早于Application所实现的,这样开发人员才能应用到具体的Lib,一个后实现的方法来调用先实现的方法,或根据其结果来作出对应操作,这样的实例便是早绑定,这也是比较普遍的一种现象;
在面向对象语言出现后,晚绑定的方法便诞生了,即:一个 先实现的方法 来调用 后实现的方法,与前者恰好相反。
观察刚刚的方法,显而易见,step1和step3就是这个操作中的算法骨架(稳定部分),step2和step4便“应该”属于算法的一些步骤(变化的部分),之所以用引号,是因为上述的写法,step2和step4,甚至main流程并不属于Lib里,下面我们经过优化,来实现一个真正的复合模版方法设计模式的实例:
class Library{
public:
void Run(){
if(step1())
step2();
for(int i=0,n=step3();i<n;++i)
step4();
step5();
}
virtual ~Library(){}
protected:
virtual void step2()=0;
virtual void step4()=0;
bool step1(){
//do something
}
int step3(){
//do something
}
void step5(){
//do something
}
};
class Application:public Library{
public:
virtual void step2(){
//do something
}
virtual void step4(){
//do something
}
};
int main(){
Library* app=new Application();
app->Run();
delete app;
}
在上述符合代码中,便是晚绑定的逻辑:程序库开发早于用户,但却将变化的具体细节交给用户来实现,我们所要做的也就仅有实现我们自己的Application类、重写对应的变化的操作并调用。
这种设计模式的特征特别明显并非常基础,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展,是代码复用方面的基本实现结构。除了可以灵活应对子步骤的变化外,“Don’t call me, let me call you(不要调用我,让我来调用你)”的反向控制结构是Template Method的典型应用。
模板方法模式的主要优点有:
- 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
- 它在父类中提取了公共的部分代码,便于代码复用。
- 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
模板方法模式的主要缺点为:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
- 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
文章读到最后,可能会有人可能会问:如果极端的方向来讲,我们使用这种模式的前提是这个框架的整体是稳定的,如果他是不稳定的,或者其中的每一步都是不稳定的(每一个步骤都是虚函数),即这个软件体系结构的所有都不稳定,那这个模式还适合吗?
在回答这个问题之前,我们要先明确一个点:即所有设计模式的假设条件:必须有一个稳定点,但不能完全稳定 。
后者是很好理解d1,如果一个设计模式是完全稳定没有变化的,那么根本也就不需要用到这个设计模式,是没有意义的;
而对于前者来说,设计模式的最大作用,即在变化和稳定之间寻找隔离点,所以一个稳定的基础和非完全稳定的基础都是必不可少的