源码地址 https://github.com/sadgeminids/TemplateMethodLearning
设计模式目的:
模板方法,在父类中定义好算法步骤顺序,将算法中的某一具体部分延迟到子类中实现,使得可以在不改变算法的前提下,将体重特定的部分改变实现。
XML类图:
模式核心概述:
从模式描述中就能知道,模板方法模式的类图很简单,仅涉及到父类和子类。
1.父类实现一个模版方法,定义具体的算法;
2.父类提供算法中可变部分的接口,供子类实现;
3.子类实现可变部分的接口
代码实现:
1.实现父类算法,提供抽象接口
namespace TemplateMethod
{
public class Weekend
{
public void Plain()
{
GetUp();
Sleep();
DoSomething();
}
public virtual void GetUp()
{
}
public virtual void Sleep()
{
}
public virtual void DoSomething()
{
}
}
}
Plain就是一个模板方法,定义了周末一天的计划。但是每个步骤具体要怎么做,又没有完全限定死,而是做成了抽象接口供子类自由实现。
2.子类具体实现
实现一个悠闲的周末类:
namespace TemplateMethod
{
public class LeisureWeekend : Weekend
{
public override void GetUp()
{
Console.WriteLine("Get up at 11 am ");
}
public override void Sleep()
{
Console.WriteLine("Sleep at 10 pm");
}
public override void DoSomething()
{
Console.WriteLine("Watch movie");
Console.WriteLine("Play Game");
Console.WriteLine("Riding");
}
}
}
再实现一个忙碌的周末类:
namespace TemplateMethod
{
public class BusynessWeekend : Weekend
{
public override void GetUp()
{
Console.WriteLine("Get up at 9 am ");
}
public override void Sleep()
{
Console.WriteLine("Sleep at 12 pm");
}
public override void DoSomething()
{
Console.WriteLine("Work...");
Console.WriteLine("Work...");
Console.WriteLine("Work...");
}
}
}
3.调用代码:
namespace TemplateMethod
{
class Program
{
static void Main(string[] args)
{
Weekend leisure = new LeisureWeekend();
leisure.Plain();
Weekend busyness = new BusynessWeekend();
busyness.Plain();
Console.ReadKey();
}
}
}
从代码也可看出模板方法模式很简单,用不了多少代码就能实现这个模式的实例。
其实,在实际工程中,绝大部分项目中或多或少都用到了该模式。因为这种模式实在是太基础,太易于被使用了。
举个简单例子,比如在卡牌游戏中,有冒险、金币、常规关卡、爬塔等几个模式,从点击主界面按钮到真正进入游戏,虽然流程基本一致,但每个模式打开界面读表、奖励提示等数据来源不一致,这时候就可以使用模板方法。
ClickButton(); // 点击某个模式按钮
OpenRewardsWindow(); // 打开不同模式的奖励界面,读取不同的奖励表
PreEnterProcess(); // 进入副本前的操作,比如扣体力、加载一些配置表
EnterMap(); // 开始进入地图
...
实现一个虚函数就是模板方法了吗?!
从某种意义上可以这样说,父类的一个虚函数本来从广义上来讲就是一个算法,这样只要有子类去实现了这个接口就可以算得上是个模板方法。但如果真这样,模板方法也就太廉价了。
在GOF的《设计模式》一书中,讲模板方法归类为行为类模式。所谓行为类,蕴含了两方面含义。
1.行为--意指这种模式重点关注的是行为
2.类--这种行为是在类中间传递的
行为如何在类中传递,只能是继承。模板方法可变部分就是在子类和父类中传递的行为。
初学者可能会将关注点放在虚函数的实现这个点上,然而就如名字一样,模板方法才是这个设计模式的真正核心价值所在。将一个算法归纳,不变的部分放入父类,将可变的部门抽象出来,用虚函数实现供子类复写。如何去设计好这个算法,去剥离可变、不可变两部分才是重点分析设计所在。如何设计好这个模板方法,关系到这个模式的稳定性。
如果仅仅是一个虚函数,可以说只包含了可变实现,并没有通用不变的部分,这样还算是一个合理的算法吗?
所以,设计好算法,尤其是某个算法整体流程非常固定,几乎不会改变,只是某部门实现比较多变灵活,不妨考虑下模版方法。
最后,用一个关联模式,回溯下之前的内容同时,也再将模板设计模式深刻入脑中。
设计模式1 Iterator模式中,我们有提到Foreach内部做了些操作,它需要一个IEnumerator对象,并且调用IEnumerator对象的Current属性和MoveNext接口。看着是不是很眼熟呢?Foreach是不是就有点像一个模版方法?!只是说这个模版方法被C#语言给隐藏起来了,但它的算法是固定了的。
我们可以大致模拟一下foreach的代码:
// foreach
IEnumerator enumerator = XXXX.GetEnumerator();
while(enumerator.MoveNext()){
var value = enumerator.Current;
}
这就是一个类似的模版方法,算法是固定的,MoveNext和Current是抽象可变的。不过略有区别的是,这个模版方法是属于类的外部方法,不是在IEnumerator类的内部。算法和抽象在不同类中实现,所以IEnumerator不能算作是实现了模版设计模式的一个类。