设计模式 学习笔记 之七

第9章 Template Method模式和Strategy模式

在许多实际问题中,我们都会碰到这样的情况:问题的解决方案有一个大体的框架或通用的流程,但是在一些具体的细节上需要具体问题具体分析,采取不同的措施或策略。换言之,我们已经构思出了一个通用的算法来解决一类问题,但是其中的某些细节又是与具体的问题相关的。我们希望能够把通用算法与具体的上下文相分离,这样,通用算法部分将是稳定的可重用的,而由具体的上下文来负责填充入相关细节,从而给出一个完整的解决方案。Template Method模式和Strategy模式正是为了这个目的而生的。我们下面先看两个例子,看看这两种模式分别是怎么用的。

例子:Template Method模式在Servlet编程中的应用

Servlet编程中最核心的一个类是HttpServlet。这个类的service()方法定义了处理HTTP请求的通用流程,因此这个方法是一个模板方法。而doGet()和doPost()方法则是service()所规定的通用流程中的两个步骤,这两个步骤是由具体的Web应用决定的,HttpServlet无法决定。因此HttpServlet只是定义出doGet()和doPost()这两个方法,供它的子类覆盖,这样的方法被称为钩子方法。在进行Servlet开发时,我们需要从HttpServlet类派生出一个子类,在这个子类中去覆盖doGet()和doPost()方法,从而与HttpServlet的service()模板方法联合起来,实现一个完整的servlet。

clip_image002

例子:Strategy模式在排序函数中的应用

在学习Java Collection框架时,我们都学习过Collections.sort()静态方法,它的声明为:

static void sort(List<T> list, Comparator<T> comparator);

这里,sort()方法定义了对所有序列式容器都适用的通用排序算法,而第2个参数则是由具体的序列式容器所决定的元素比较策略。因此我们可以得出如下的设计思路。

clip_image004

对比Template Method模式与Strategy模式

有了前面的两个例子,我们下面来对Template Method模式和Strategy模式做一下比较。Template Method模式与Strategy模式都是用于解决将通用算法与具体上下文相分离的问题,但是两者在解决这个问题上的基本思路是不同的。Template Method模式使用“继承”来解决这个问题,它把通用算法写成模板方法并放入基类中,并声明了留待子类实现的若干钩子方法。子类需要做的就是按照具体的上下文细节去定义这些钩子方法,从而与基类的模板方法联合起来实现一个完整的功能或流程。Strategy模式则是使用“委派”的方法来把通用算法与具体上下文分离,即把通用算法封装到一个类中,而把与具体上下文相关的细节封装到另一个class hierarchy中,并让通用算法类来使用这个class hierarchy,同样达到实现一个完整流程的目的。

Template Method模式和Strategy模式的典型应用场合是有不同的。我们用下面的表格来总结这种不同。

Template Method模式

Strategy模式

应用场合

通用算法的骨架已经确定,但是其中的单个步骤是与具体上下文相关的,这时适宜应用Template Method模式。

通用算法的骨架已经确定,但是要在不同的上下文中应用不同的处理方式或业务规则,这时适宜使用Strategy模式。

实现手段

继承。因此父类与子类之间是强耦合关系。

委派。因此业务规则(即策略)与业务规则的使用者之间是松散耦合的。

备注

父类为子类声明了钩子方法,这些钩子方法就对应于由具体上下文决定的单个步骤。钩子方法常以doXXX()命名,通常没有返回值。

如果有在几种业务规则之间切换的需求,就应该考虑使用Strategy模式了。

了解了Template Method模式和Strategy模式之间的不同应用场合,我们下面再来看两个例子,看看怎么使用这两种模式。

例子:应用程序的通用流程

我们有许多的应用程序都遵循下面这样的流程:

run()

{

do initialization

while (has more work to do)

{

        do more work

}

do cleanup

return;

}

实际上,这个run()方法就可以看作是一个模板方法,由此我们可以基于它来设计一个应用程序模板。

clip_image006

例子:为订单计算税费

假设我们在开发一个电子商务系统。在该系统中要处理来自用户的订单,并且把订单打印出来。打印订单时,要求同时也把订单的税费打印出来。由于目前的订单都来自美国,因此税费按照美国税费计算规则来计算。在这样的需求之下,我们可以得到下面的设计。

clip_image008

现在,新的需求来了。随着公司业务的扩展,现在在了来自中国的订单。而来自中国的订单则需要使用中国的税费计算规则来计算了。怎么来应对这个需求变化?一个直观的办法是:在calcTax()方法中加入if-else逻辑,对于美国订单,采用美国规则,而对于中国订单,采用中国规则。

这是一个好办法吗?目前看起来还可以,不过如果以后还有新的国家的订单加入呢?那么calcTax()就会成为一个冗长的if-elseif-elseif结构。并且,所有的税费规则都集中到了calcTax()这个方法中,这对维护人员来说简直就是噩梦。

实际上,对比刚才我们讲过的Strategy模式的应用场合,我们不难发现,这个问题是适宜于Strategy模式来解决的。因为现在面对的问题就是要在不同的上下文中应用不同的业务规则。有了这个认识,我们就能够得到下面的这个设计。

clip_image010

小结

在许多时候我们都会碰到要将通用算法与具体上下文相分离的需求,Template Method模式和Strategy模式都是处理这种需求的方法,因此在多数情况下,这两种方法可以互相替换。但是,我们建议认清楚这两种模式各自适宜使用的场合。正如前面所述,Template Method模式较适合于通用算法中的单个步骤需要被填充的情况,而Strategy模式则较适合于由具体上下文决定选择某个业务规则或处理方式的情况。

posted @ 2011-04-25 21:35  李嘉 (Justin)  阅读(152)  评论(0编辑  收藏  举报