模板方法模式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板方法模式相关的知识,希望对你有一定的参考价值。
定义:在一个方法中定义一个算法的骨架。而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法和结构的情况下,重新定义方法中的某些步骤。
一:要点
模板方法定义了算法的步骤,把这些步骤的实现延迟到子类。
模板方法模式为我们提供了一种代码复用的重要技巧。
模板方法的抽象类可以定义具体的方法,抽象方法和钩子。
钩子是一种方法,他在抽象类中不做事,或者只做默认的事。子类可以选择要不要去覆盖他。
为了防止子类改变模板方法中的算法,可以将模板方法声明为final。
好莱坞原则告诉我们,将决策权放在高层的模块中,以便决定如何及何时调用低层模块。
策略模式与模板方法都是用封装算法,一个用组合,一个用继承。
工厂方法是模板方法的一个特殊的版本
二:模板方法例子
在敲代码时,累了喝杯咖啡或者喝杯茶,会精神倍增。其实无论咖啡还是茶在冲的时间都是有讲究的。这个在本文不是重点。下面分别描述一下冲泡咖啡和冲泡茶的过程:
两种茶其分别的做法如下代码:
1 public class Coffee
2 {
3 public void PrepareRecipe()
4 {
5 //烧水
6 BoilWater();
7 //冲咖啡
8 BrewCoffeeGrinds();
9 //倒入茶杯中
10 PourInCup();
11 //加入糖和咖啡
12 AddSugarAndMilk();
13 }
14 public void BoilWater()
15 {
16 Console.WriteLine("烧水");
17 }
18
19 public void BrewCoffeeGrinds()
20 {
21 Console.WriteLine("冲咖啡");
22 }
23
24 public void PourInCup()
25 {
26 Console.WriteLine("倒入杯子中");
27 }
28
29 public void AddSugarAndMilk()
30 {
31 Console.WriteLine("加糖和牛奶");
32 }
33 }
34
35 public class Tea
36 {
37 public void PrepareRecipe()
38 {
39 //烧水
40 BoilWater();
41 //泡茶
42 SteepTeaBag();
43 //倒入茶杯中
44 PourInCup();
45 //加入柠檬
46 AddLemon();
47 }
48 public void BoilWater()
49 {
50 Console.WriteLine("烧水");
51 }
52
53 public void SteepTeaBag()
54 {
55 Console.WriteLine("泡茶");
56 }
57
58 public void PourInCup()
59 {
60 Console.WriteLine("倒入杯子中");
61 }
62
63 public void AddLemon()
64 {
65 Console.WriteLine("加柠檬");
66 }
67 }
在面向对象语言中,是要讲求复用的,现在烧水和带入杯子的方法显然是重复的,这样就不符合对象村的村规——复用。 对比两种做法,都是需要四个步骤,能不能把相同的使用一个基类,不同的部分分别由自己的去实现。其类图如下
如果再细致观察的话,我们的冲咖啡和泡茶以及加入咖啡和牛奶都是属于差不多动作相同的。所以可以继续抽象,抽象后的方法大致如此:
其实得出的也就是我们的模板方法。下面来看看模板方法模式的类图:
三、模板方法类图
从类图可以看到我们把共用的方法放在抽象类中,用于复用。把不确定的方法,放入到具体类中,以便让具体类可以很好的构造自己的方法。除此之外,还有个重点是无论是构造好的方法也好,还是抽象的方法也好,都会被装入到一个TemplateMethod方法中。就像我们平时的做项目也是如此,大致的步骤需要:需求分析——>编码——>测试,这些最基本的过程。但是具体的每一步,可能都是不同的,但是做项目的过程的几个步骤是基本不变的,把步骤抽象成了模板,以后做项目的时间,都按这个步骤去做。这个就是我们的模板方法模式。到这里还是把我们的茶和咖啡沏好。
基类代码:
public abstract class CoffeeinBeverage1
{
public void BoilWater()
{
Console.WriteLine("烧水");
}
public void PrepareRecipe()
{
BoilWater();
Brew();
PourInCup();
AddCondiments();
}
public void PourInCup()
{
Console.WriteLine("倒入杯子中");
}
public abstract void Brew();
public abstract void AddCondiments();
}
咖啡代码:
public class Coffee1 : CoffeeinBeverage1
{
public override void Brew()
{
Console.WriteLine("冲咖啡");
}
public override void AddCondiments()
{
Console.WriteLine("加糖和牛奶");
}
茶代码:
public class Tea1 : CoffeeinBeverage1
{
public override void Brew()
{
Console.WriteLine("泡茶");
}
public override void AddCondiments()
{
Console.WriteLine("加柠檬");
}
}
四、模板方法注意的问题
在上面的茶水和咖啡中,现在是看起来能喝了,但是有个问题就是有些人喝咖啡喜欢不加任何调味料的。那么我们硬是给客人加,肯定是会生气的。为了满足这个要求,设计模式提供的有这个解决方案——使用钩子。具体什么是钩子,我们小的时间的课本上有个猴子捞月亮的图片:
每个猴子不仅会被其他猴子拉住,还会伸出一个手去拉其他猴子。但最后一个手是没有拉其他猴子了,但是还是要伸下去的,以防碰不到月亮。
同样不知道方法能不能用得到,但是有对应的方法,具体的什么时间用得到,什么时间用不到,根据条件来判断。
下面来看看钩子如何在模板方法模式中使用的:
基类代码:
public void PrepareRecipe()
{
BoilWater();
Brew();
PourInCup();
//有个判断方法来添加调料
if (WantCondiments())
{
AddCondiments();
}
}
/// <summary>
/// 加入一个方法,用来判断是否需要加调料
/// </summary>
/// <returns></returns>
public virtual bool WantCondiments()
{
return true;
}
在子类中,可以通过不同的方式类覆盖WantCondiment()方法。用来表示是否要加调料的标准,在此方法中注意必须加入virtual关键字,以便子类中使用override重写。下面看看咖啡中的方法:
public override bool WantCondiments()
{
return false;
}
下面是测试代码:
class Program
{
static void Main(string[] args)
{
CoffeeinBeverage1 coffeninBeverage = new Coffee1();
coffeninBeverage.PrepareRecipe();
Console.ReadKey();
}
}
输出结果:
发现已经去掉了调料。
五、模板方法模式和策略模式以及工厂方法的对比
在看模板方法的时间,很容易想到工厂方法。因为他们都是让具体的实现放在子类中,但是工厂方法主要是生产出产品,然后去应用产品。模板方法是在于依赖子类中的步骤中的其中几个步骤,具体的步骤已经在基类中写好了。
同样模板方法模式和策略模式都是封装算法。但是策略模式中的每个策略都是单独的一个类。可以随时去更改策略。模板方法模式虽然也是封装了算法,其实主要在于封装步骤,具体的实现是根据依靠各个子类。
除此之外,模板方法模式还涉及到一个好莱坞原则:
不要给我打电话,我会主动和你打电话。
在模板方法模式中扮演好莱坞的角色是抽象类,子类是演员的角色。
一般需要调用子类中的方法都已经在模板中定义好了,需要时,会主动联系各个步骤,最好在子类中不要去调用抽象类中的方法具体方法,以防止子类和父类的调用的凌乱。
以上是关于模板方法模式的主要内容,如果未能解决你的问题,请参考以下文章
模板方法模式:剖析模板方法在JDKTomcatMybatis等开源框架中的应用
模板方法模式:剖析模板方法在JDKTomcatMybatis等开源框架中的应用