设计模式的艺术之道–模板方法模式
声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐
本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).
本系列全部源码均在文末地址给出。
本系列开始讲解行为型模式,关注如何将现有类或对象组织在一起形成更加强大的结构。
- 行为型模式(Behavioral Pattern)
关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责
不仅仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分 - 类行为型模式
使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责 - 对象行为型模式
使用对象的关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责
11种常见的行为型模式
模板方法模式–对象间的联动
在现实生活中,很多事情都包含几个实现步骤,例如请客吃饭,无论吃什么,一般都包含点单、吃东西、买单等几个步骤,通常情况下这几个步骤的次序是:点单 –> 吃东西 –> 买单。在这三个步骤中,点单和买单大同小异,最大的区别在于第二步——吃什么?
1.1定义
-模板方法模式 (Template Method Pattern):定义一个操作中算法的框架,而将一些步骤延迟到子类中。模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式。
将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果
1.2情景实例
问题描述
-利息计算模块
某软件公司欲为某银行的业务支撑系统开发一个利息计算模块,利息计算流程如下:
(1) 系统根据账号和密码验证用户信息,如果用户信息错误,系统显示出错提示;
(2) 如果用户信息正确,则根据用户类型的不同使用不同的利息计算公式计算利息(如活期账户和定期账户具有不同的利息计算公式);
(3) 系统显示利息。
初步思路分析
第一步和第三步都相同,区别是第二部分的利息计算方式,使用模板方法模式设计该利息计算模块。
Account充当抽象类角色,CurrentAccount和SavingAccount充当具体子类角色
UML类图
实例关键源代码
abstract class Account
{
//基本方法——具体方法
public bool Validate(string account, string password)
{
Console.WriteLine("账号:{0}", account);
Console.WriteLine("密码:{0}", password);
//模拟登录
if (account.Equals("张无忌") && password.Equals("123456"))
{
return true;
}
else
{
return false;
}
}
//基本方法——抽象方法
public abstract void CalculateInterest();
//基本方法——具体方法
public void Display()
{
Console.WriteLine("显示利息!");
}
//模板方法
public void Handle(string account, string password)
{
if (!Validate(account,password))
{
Console.WriteLine("账户或密码错误!");
return;
}
CalculateInterest();
Display();
}
}
class CurrentAccount : Account
{
//覆盖父类的抽象基本方法
public override void CalculateInterest()
{
Console.WriteLine("按活期利率计算利息!");
}
}
class Program
{
static void Main(string[] args)
{
Account account;
//读取配置文件
string subClassStr = ConfigurationManager.AppSettings["subClass"];
//反射生成对象
account = (Account)Assembly.Load("TemplateMethodSample").CreateInstance(subClassStr);
account.Handle("张无忌", "123456");
Console.Read();
}
}
钩子方法的使用
模板方法模式中,在父类中提供了一个定义算法框架的模板方法,还提供了一系列抽象方法、具体方法和钩子方法,其中钩子方法的引入使得子类可以控制父类的行为。最简单的钩子方法就是空方法
public virtual void Display() { }
举例说明:菜鸟软件公司欲为销售管理系统提供一个数据图表显示功能,该功能的实现包括如下几个步骤:
(1) 从数据源获取数据;
(2) 将数据转换为XML格式;
(3) 以某种图表方式显示XML格式的数据。
该功能支持多种数据源和多种图表显示方式,但所有的图表显示操作都基于XML格式的数据,因此可能需要对数据进行转换,如果从数据源获取的数据已经是XML数据则无须转换。
abstract class DataViewer
{
//抽象方法:获取数据
public abstract void GetData();
//具体方法:转换数据
public void ConvertData()
{
Console.WriteLine("将数据转换为XML格式。");
}
//抽象方法:显示数据
public abstract void DisplayData();
//钩子方法:判断是否为XML格式的数据
public virtual bool IsNotXMLData()
{
return true;
}
//模板方法
public void Process()
{
GetData();
//如果不是XML格式的数据则进行数据转换
if (IsNotXMLData())
{
ConvertData();
}
DisplayData();
}
}
class XMLDataViewer : DataViewer
{
//实现父类方法:获取数据
public override void GetData()
{
Console.WriteLine("从XML文件中获取数据。");
}
//实现父类方法:显示数据
public override void DisplayData()
{
Console.WriteLine("以柱状图显示数据。");
}
//覆盖父类的钩子方法
public override bool IsNotXMLData()
{
return false;
}
}
class Program
{
static void Main(string[] args)
{
DataViewer dv;
dv = new XMLDataViewer();
dv.Process();
}
}
}
1.3模式分析
动机和意图
- 如何定义一个类似流程的具体过程步骤,但其中的具体细节实现有可能不同?
一般结构
模板方法模式包含2个角色:
-AbstractClass(抽象类):包含基本算法的流程,以及抽象的细节方法接口,有可能包含钩子方法。
-ConcreteClass(具体子类):实现父类的细节方法,重写父类的钩子方法。模板方法模式UML类图
改进后的优点
- 在父类中定义一个算法基本流程,而由它的子类来实现细节的处理
- 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行
- 更换和增加新的子类很方便,符合单一职责原则和开闭原则
现存的缺点
- 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统会更加庞大
适用场景
(1) 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现
(2)各子类中公共的行为应被提取出来,并集中到一个公共父类中,以避免代码重复
(3)需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制
举例:小王面馆,炒菜的过程 洗菜 切菜 炒菜 加调料(有的顾客口味不一) 出锅
实例源代码
GitHub地址
百度云地址:链接: https://pan.baidu.com/s/1c230XmK 密码: nkst