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 模式。 |