template method模式和strategy模式都是关注对象的行为的,按照依赖倒置的方法来分离抽象和具体的实现,但是两者的实现方法不同。template method模式应用了面向对象中继承的思想,而strategy模式则应用了委托的思想,从template method模式和strategy模式中也可以看到面向对象世界中abstract类和interface的异同。实际上这两种模式我们经常会用到,只是可能没有意识到而已。
Template method的常见用法是将运算的骨架放在基类中,然后将某些具体的算法放到子类里面实现,这样就可以使得子类不改变算法的结构即可重定义该算法的某些特定步骤。
Template method的一个例子:Report服务。
假定系统中有一系列Report,Booking Report, Billing Report, Aging Report…。这些Report参数不一样,界面不一样,逻辑不一样,但是也有一些有共同点,验证权限,结果输出,Report载入步骤等等。那么为了避免重复代码,有一个较好的可扩充的结构,我们把这些共同点extract到一个基类中ReportBase,简单示例如下:
public class ReportBase
{
public void OpenReport(string reportId)
{
if(UserIsInRole()==true)
{
Initialize();
LoadSavedReport();
}
}
private void Initialize()
{
InitializeControl();
SetDefaultValue();
}
protected abstract void InitializeControl()
{
}
protected abstract void LoadSavedReport ()
{
}
protected virtual void SetDefaultValue ()
{
}
}
在ReportBase中定义了OpenReport()的逻辑,先验证用户权限,然后初始化Report的页面上的控件,设置缺省值,载入用保存的Report。对于每个具体的report来说,界面和参数是不同的,要实现InitializeControl()和LoadSavedReport()方法。
ReportBase.OpenReport()是一个Template,定义了算法的每一步,并且允许子类提供其中某些步的具体实现。
我们可以看到Template Method的主要特点是,将各个子类中不变的行为提取到基类中实现,可变的部分留给子类自己实现,而且基类中定义了一个相对稳定的结构,也就是一个模版Template,模版中的某些步骤留待子类来实现,基类还可以控制子类的扩展,允许某些子类在某些点上作扩展。对于基类来说意义在于控制整个结构,减少重复代码,对于子类来说意义在于不改变算法的机构可以方便的提供一种实现。
从Template Method中可以看到Framework和控制反转的精神,不是每个具体做事情的子类调用所需的Library完成某个行为,而是提供具体实现,让高层的代码来调用。
(Hollywood Principle: 当高层的模块依赖低层的模块时,由高层的模块决定何时以及如何调用底层模块,也就是说高层模块对底层模块讲:Don’t call me, we’ll call you)
当然Template Method模式和strategy模式在现在看来都已经是很自然的都东西。
应用Template Method需要先对代码逻辑作分析,哪些放到基类,哪些留给子类,当然这属于OO世界最基本的东西。如果现有的代码重构到Template Method可以参考《Refactoring》Dealing with Generalization一章。
应用Template Method需要注意的是基类需要指明那些行为是子类必须重定义的,哪些行为是允许子类重定义的(Abstract method和Hook Method),具体到C#的语法就是abstract和virtual定义。而且需要仔细评估那些子类必须重定义的行为,避免子类中需要做过多的重定义工作。对于可以允许子类重定义的行为来说好比提供了一个hook,在基类中通常是一个空操作,允许子类在这一点上扩展基类的行为。
另外一点需要注意的是继承属于很强的耦合关系,过多的继承关系会使系统变得僵化难以变化,往往会用对象的组合来代替继承。
一个替代继承的模式是strategy模式。
Template Method使用了对象继承,而继承是一种很强的对象和对象之间的耦合关系,底层模块还是依赖于高层模块,比如子类要知道哪些abstract method要重写,哪些hook method可以做扩展,哪些基类资源可以利用。
strategy模式使用对象之间的组合关系来代替继承,进一步减弱高层模块和底层模块之间的依赖,让底层独立于高层,使其完全符合更符合DIP的原则, 这样底层代码不需要了解高层代码是怎么工作的,高层也不需要知道底层的实现细节,
相对于Template Method模式来说strategy模式革命的更彻底一些。
还拿Report来作一个简单的例子。
Report的输出可以是Html格式的,可以是Word格式的,也可以是PDF格式的,我们可以定义一个Interface:IReportPublisher,在IReportPublisher中定义高层模块和低层模块之间的调用协议。(或者说Contract)
Interface IReportPublisher {public void Publish()}
HTMLReportPublisher : IReportPublisher
WordReportPublisher : IReportPublisher
PDFReportPublisher : IReportPublisher
Report
{
Public void GenerateReport()
{
…
reportPublisher = MyContext.GetService(…)
reportPublisher.Publish(reportData)
…
}
}
当然实现strategy模式也有代价,将Template Method改造成strategy模式后,架构中层次变复杂了,一些Template Method模式原有的特性就没有办法利用了,比如子类可以调用基类一些资源。
应用任何一种模式都会有代价,学习模式时,明白为什么这么做比明白怎么实现更重要,了解模式带来的收益的同时也要了解你要付出的代价。模式也是一直随着技术的发展在发展的,有些模式在慢慢消亡,有些新的模式出现,有些模式被新的编程语言天然支持,有些模式的形式发生了变化,Gof的《设计模式》里的一些观点现在来说已经是有些过时了。
strategy模式和Template Method模式的对比还体现了面向对象的另一个思想,解决对象之间的协作问题应用对象组合优先于对象继承。在面向对象的初期,人们非常看重对象继承,继承一个类,就可以重用该类的代码,把很多类的代码抽取到一个基类中就可以去掉代码重复,创建一个子类,改变一点点就能创造出一个能实现新功能的类出来,通过继承我们可以建立完整的软件结构分类,每一层都可以重用该层以上的代码,这看起来很美好,到后来人们才慢慢发现继承非常容易被过度使用,而过度使用带来的收益比代价要高的多,所以我们减少对继承的使用,用组合和委托来代替继承。
strategy模式和Template Method模式都面对一个问题,在运行时怎么创建某个子类或者某个实现了某个interface的类的实例,那就涉及关于创建对象实例的模式:Singleton模式和Factory模式。