模版方法模式
一、 基本概述
下面列出咖啡、茶的冲泡方法。
1.咖啡冲泡方法
(1) 把水煮沸
(2) 用沸水冲泡咖啡
(3) 把咖啡倒进杯子
(4) 家牛奶和糖
2.茶的冲泡方法
(1) 把水煮沸
(2) 用沸水浸泡茶叶
(3) 把茶倒进杯子
(4) 加柠檬
在使用代码来完成这些方法时,我们一般想到的创建2个类(咖啡、茶类)来单独实现这四个步骤。或者更好点是创建一个饮料父类来共享第一步与第三步,然后继承父类实现各自饮料的第二步与第四步。那么还有更好一点的方式来处理上面的问题吗?有,可以使用模版方法模式。
二、详细说明
1.模版方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
这个模式是用来创建一个算法的模版。什么是模版?如你所见,模版就是一个方法。更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,有子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现,钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。
问:当我创建一个模版方法时,怎么才能知道什么时候该使用抽象方法,什么时候使用钩子呢?
答:当你的子类“必须”提供算法中某个方法或步骤的实现时,就使用抽象方法。如果算法的这个部分是可选的,就用钩子。如果是钩子的话,子类可以选择实现这个钩子,但并不强制这么做。
问:使用钩子真正的目的是什么?
答:钩子有几种用法。钩子可以让子类实现算法中可选的部分,或者在钩子对于子类的实现并不重要的时候,子类可以对此钩子置之不理。钩子的另一个用法,是让子类能够有机会对模版方法中某些即将发生的步骤作出反应。钩子也可以让子类有能力为其抽象类作一些决定。
如在Asp.net MVC框架中,你创建一个控制器,默认都是继承Controller类,而Controller类中就有钩子,如OnActionExecuted、OnActionExecuting、OnResultExecuted、OnResultExecuting等,这些钩子能够在你的控制器中进行实现,以便控制或加工对请求的动作与返回的执行。
2.设计原则:好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖底层组件,而底层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件有依赖底层组件时,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。
在好莱坞原则之下,我们允许底层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些底层组件。换句话说,高层组件对待底层组件的方式是“别调用我们,我们会调用你”。
好莱坞原则和模版方法之间的连接其实还算明显:当我们设计模版方法模式时,我们告诉子类,“不要调用我们,我们会调用你”。
问:底层组件不可以调用高层组件中的方法吗?
答:并不尽然,事实上,底层组件在结束时,常常会调用从超类中继承来的方法。我们所要做的是,避免让高层和底层组件之间有明显的环状依赖。
模版方法模式是一个很常见的模式,到处都是。尽管如此,你必须拥有一双锐利的眼镜,因为模版方法有许多实现,而它们看起来并不一定和书上所说的设计一致。
这个模式很常见是因为对创建框架来说,这个模式好用。有框架控制如何做事情,而由你(使用这个框架的人)指定框架算法中每个步骤的细节。
如在C#数组中,有Array类提供一个Sort()方法用来排序,该方法有2个参数(一个是数组,一个是IComparer 接口),Sort方法提供了排序算法,实现IComparer 接口的参数提供了数组元素怎么进行比较大小。
问:这真的是一个模版方法模式吗?
答:我们都知道,荒野中的模式并非总是如同教科书例子一般地中规中矩,为了符合当前的环境和实现的约束,它们总是要被适当地修改。这个Array类的Sort()方法的设计者受到一些约束,通常我们无法设计一个类继承C#数组,而Sort()方法希望能够适用于所有的数组(每个数组都是不同的类)。所以它们定义了一个静态方法,而由被排序的对象内的每个元素自行提供比较大小的算法部分。所以,这虽然不是教科书上的模版方法,但它的实现仍然符合模版方法模式的精神。再者,由于不需要继承数组可以使用这个算法,这样使得排序变得更有弹性、更有用。
4.总结:
1.好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用底层模块。
2.你将咋真实世界代码中看到模版方法模式的许多变体,不要期待它们全都是一眼就可以被你认出的。
3.策略模式和模版方法模式都封装算法,一个用组合,一个用继承。
4.工厂方法是模版方法的一种特殊版本。
三、代码列表
public abstract class CaffeineBeverage { public void PrepareRecipe() { BoilWater(); Brew(); PourInCup(); AddCondiments(); Hook(); } protected abstract void Brew(); protected abstract void AddCondiments(); private void BoilWater() { Console.WriteLine("Boiling water"); } private void PourInCup() { Console.WriteLine("Pouring into cup"); } protected virtual void Hook() { } } public class Coffee:CaffeineBeverage { protected override void Brew() { Console.WriteLine("冲泡咖啡"); } protected override void AddCondiments() { } } public class Tea : CaffeineBeverage { protected override void Brew() { Console.WriteLine("浸泡茶"); } protected override void AddCondiments() { Console.WriteLine("加柠檬"); } }
---------以上内容根据《Head First Design Mode》进行整理