在现实生活中,很多事情都需要经过几个步骤才能完成,例如请客吃饭,无论吃什么,一般都包含:点单、吃东西、买单等几个步骤,通常情况下这几个步骤的次序是:点单=>吃东西=>买单。在这3个步骤中,点单和买单大同小异,最大的区别在于第2步-吃什么?吃面条和吃满汉全席可大不相同。

在软件开发中,有时候也会遇到类似的情况,某个方法的实现需要多个步骤(类似于“请客”),其中有些步骤是固定的,而有些步骤则存在可变性。为了提高代码复用性和系统灵活性,可以使用一种称之为模板方法模式的设计模式来对这类情况进行设计。

模板方法模式(Template Method) 学习难度:★★☆☆☆ 使用频率:★★★☆☆

一、银行利息计算模块的设计

1.1 需求背景

Background:M公司欲为某银行的业务支撑系统开发一个利息计算模块,利息计算流程如下:

(1)系统根据账号和密码验证用户信息,如果用户信息错误,系统显示错误提示。

(2)如果用户信息正确,则根据用户类型的不同使用不同的利息计算公式计算利息(例如活期账户和定期账户具有不同的利息计算公式)

(3)系统显示利息。

1.2 初始设计

  M公司开发人员根据需求设计了一个Account类,在其中定义了3个方法实现上述3个步骤,其核心代码如下所示:

    public class Account
{
// 验证用户信息
public bool Validate(string account, string password)
{
// 具体代码省略
} // 计算利息
public void CalculateInterest(string type)
{
if (type.Equals("Current", StringComparison.OrdinalIgnoreCase))
{
// 按活期利率计算利息,代码省略
}
else if (type.Equals("Saving", StringComparison.OrdinalIgnoreCase))
{
// 按定期利率计算利息,代码省略
}
} // 显示结果
public void Display()
{
// 具体代码省略
}
}

  客户端可以通过调用Account类实现完整的利息计算流程,核心代码片段如下:

    public class Client
{
public static void Main()
{
Account account = new Account();
if (account.Validate("张无忌", "")) // 验证用户
{
account.CalculateInterest("Current"); // 计算利息
account.Display(); // 显示利息
}
}
}

  But,不难发现,该设计实现有以下两个问题:

  (1)系统可扩展性较差 => 如果需要增加一种新类型的用户,例如“小额贷款用户”,在系统中需要对应增加一种新的利息计算方法,不得不修改Account类的源代码,在CalculateInterest方法中增加新的判断逻辑,违背了开闭原则。

  (2)客户端需要逐个调用Account类中定义的方法,而且需要了解这些方法的执行与否,否则容易出错 => 例如Account类中的3个方法的次序为:Validate() => CalculateInterest() => Display(),如果不按次序调用,可能会导致结果出错。

  针对问题(1),可以使用Account类的子类来解决,在子类中覆盖父类的CalculateInterest()方法,实现扩展。但是针对问题(2),即使使用的是子类,也无法解决该问题。是否存在一种技术能够一次解决问题(1)和问题(2)?

二、模板方法模式概述

2.1 模板方法模式简介

  模板方法可以算是最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系,其定义如下:

模板方法(Template Method)模式:定义一个操作中算法的框架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的特定步骤。模板方法是一种行为型模式。

2.2 模板方法模式结构

  模板方法模式结构比较简单,其核心是抽象类和其中的模板方法的设计,其结构如下图所示:

  (1)AbstractClass(抽象类):在抽象类中定义了一系列基本操作(Primitive Operations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重新定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架。

  (2)ConcreteClass(具体子类):抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。

  多说无益,下面我们直接看代码实现,一眼就可以明白。

三、重构银行利息计算模块设计

3.1 重构后的设计结构

  其中,Account充当抽象类角色,CurrentAccount与SavingAccount充当具体子类角色。=> 是不是简单得不行?

3.2 具体代码实现

  (1)抽象类:Account

    /// <summary>
/// 抽象类:Account
/// </summary>
public abstract class Account
{
// 基本方法 - 具体方法
public bool Validate(string account, string password)
{
Console.WriteLine("账号 : {0}", account);
Console.WriteLine("密码 : {0}", password); if (account.Equals("张无忌") && password.Equals(""))
{
return true;
}
else
{
return false;
}
} // 基本方法 - 抽象方法
public abstract void CalculateInterest(); // 基本方法 - 具体方法
public void Display()
{
Console.WriteLine("显示利息");
} // 基本方法 - 钩子方法
public virtual bool IsAllowDisplay()
{
return true;
} // 模板方法
public void Handle(string account, string password)
{
if (!Validate(account, password))
{
Console.WriteLine("账户或密码错误,请重新输入!");
return;
} CalculateInterest(); if (IsAllowDisplay())
{
Display();
}
}
}

  (2)具体子类:CurrentAccount和SavingAccount

    /// <summary>
/// 具体子类:CurrentAccount => 活期账户类
/// </summary>
public class CurrentAccount : Account
{
// 重写父类的抽象基本方法
public override void CalculateInterest()
{
Console.WriteLine("按活期利率计算利息!");
} // 重写父类的钩子方法
public override bool IsAllowDisplay()
{
return base.IsAllowDisplay();
}
} /// <summary>
/// 具体子类:SavingAccount => 定期账户类
/// </summary>
public class SavingAccount : Account
{
// 重写父类的抽象基本方法
public override void CalculateInterest()
{
Console.WriteLine("按定期利率计算利息!");
} // 重写父类的钩子方法
public override bool IsAllowDisplay()
{
return false;
}
}

  (3)客户端测试

    public class Program
{
public static void Main(string[] args)
{
Account account = AppConfigHelper.GetAccountInstance() as Account;
if (account != null)
{
account.Handle("张无忌", "");
} Console.ReadKey();
}
}

  这里,我们将具体子类配置在了配置文件中:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="AccountType" value="Manulife.ChengDu.DesignPattern.TemplateMethod.SavingAccount, Manulife.ChengDu.DesignPattern.TemplateMethod" />
</appSettings>
</configuration>

  其中,AppConfigHelper类用于获取配置文件中的具体子类的实例:

    public class AppConfigHelper
{
public static string GetAccountTypeName()
{
string factoryName = null;
try
{
factoryName = System.Configuration.ConfigurationManager.AppSettings["AccountType"];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return factoryName;
} public static object GetAccountInstance()
{
string assemblyName = AppConfigHelper.GetAccountTypeName();
Type type = Type.GetType(assemblyName); var instance = Activator.CreateInstance(type);
return instance;
}
}

  编译运行后的结果如下图所示:

  

  如果这时我们需要更换具体子类,那么无须更改源代码,只需修改配置文件:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="AccountType" value="Manulife.ChengDu.DesignPattern.TemplateMethod.CurrentAccount, Manulife.ChengDu.DesignPattern.TemplateMethod" />
</appSettings>
</configuration>

  重新运行客户端后的结果如下图所示:

  

四、模板方法模式总结

4.1 主要优点

  模板方法中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责和开闭原则。

4.2 主要缺点

  需要为每一个基本方法的不同实现一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也会更加抽象。

4.3 应用场景

  (1)对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类来实现。

  (2)需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

参考资料

  

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

设计模式的征途—17.模板方法(Template Method)模式的更多相关文章

  1. 一天一个设计模式——模板方法(Template Method)模式

    一.模式说明 现实世界中的模板是用于将事物的结构规律予以固定化.标准化的成果,它体现了结构形式的标准化.例如镂空文字印刷的模板,通过某个模板印刷出来的文字字体大小都是一模一样,但是具体使用什么材质的颜 ...

  2. 行为型设计模式之模板方法(TEMPLATE METHOD)模式 ,策略(Strategy )模式

    1 模板方法(TEMPLATE METHOD)模式: 模板方法模式把我们不知道具体实现的步聚封装成抽象方法,提供一些按正确顺序调用它们的具体方法(这些具体方法统称为模板方法),这样构成一个抽象基类.子 ...

  3. 封装算法: 模板方法(Template Method)模式

    template method(模板方法)模式是一种行为型设计模式.它在一个方法中定义了算法的骨架(这种方法被称为template method.模板方法),并将算法的详细步骤放到子类中去实现.tem ...

  4. 《图解设计模式》读书笔记2-1 Template Method模式

    目录 模板方法模式 类图 思想: 模板方法模式 在父类中定义流程,在子类中实现具体的方法. 类图 代码 //抽象类 public abstract class AbstractDisplay { pu ...

  5. Java设计模式透析之 —— 模板方法(Template Method)

    今天你还是像往常一样来上班,一如既往地開始了你的编程工作. 项目经理告诉你,今天想在server端添加一个新功能.希望写一个方法.能对Book对象进行处理.将Book对象的全部字段以XML格式进行包装 ...

  6. 设计模式Template Method模式(Template Method)摘录

    23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例.怎样创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化托付给还 ...

  7. 设计模式 Template Method模式 显示程序猿的一天

    转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/26276093 不断设计模式~ Template Method模式 老套路,看高清 ...

  8. 宋宝华:Linux设备驱动框架里的设计模式之——模板方法(Template Method)

    本文系转载,著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 作者: 宋宝华 来源: 微信公众号linux阅码场(id: linuxdev) 前言 <设计模式>这本经典 ...

  9. 设计模式二--模板方法Template method

    模式分类: 书籍推荐:重构-改善既有代码的设计 重构获得模式 设计模式:现代软件设计的特征是"需求的频繁变化".设计模式的要点是 "寻找变化点,然后在变化点处应用设计模式 ...

随机推荐

  1. (NO.00001)iOS游戏SpeedBoy Lite成形记(十三)

    游戏特效部分就先这样了,因为毕竟是Lite版本,而且是第一个App,所以咱们把主要精力放在游戏可玩逻辑上吧(虽然已经厚颜无耻的加了不少特效了). 说句题外话:游戏美工是独立开发者不可逾越的鸿沟,是无法 ...

  2. 反对网抄,没有规则可以创建目标"install" 靠谱解答

    在ubuntu下遇到这个问题,原因其实很简单,你不能用WINDWOS下的方法用图形方式打开,然后点了一下按扭"解压缩",生成了一个文件夹. 的确,这个文件夹看起来和正常的没有什么区 ...

  3. 将studio项目 转换为eclipse项目

    总会有些奇怪的事情,比如,有的人就有将studio项目 转换为eclipse项目的需求 首先,不要因为编译原因而放弃.studio项目是完全可以转换成eclipse的 本站的开源代码板块有很多项目都是 ...

  4. Dynamics CRM ISV文件夹禁用后的解决方案

    众所周知微软在CRM2011的12补丁后取消了对ISV文件夹的支持,那我们自定义开发的一些web应用或者是想部署个服务该怎么办,有的选择了另开一个站点发布.我们以服务为例这样的另开站点的发布方式会导致 ...

  5. 【freeradius2.x】 安装和学习

    虚拟机中centos 安装和学习 radius2 版本是2.2.x 的使用等知识 安装 为了测试方面,yum安装 yum -y install freeradius* 配置文件的位置是 /etc/ra ...

  6. java面试之常见编程题

    1.编程实现:二分搜索算法 解答: public class SearchTest { /** 被搜索数据的大小 */ private static final int size = 5000000; ...

  7. unity 球体表面平均分割点

    之前看了别人的一份源码,讲到了球体表面平均分割点,于是也好奇去查了一下算法,自己写不出来,借用算法在unity写了一个小demo using UnityEngine; using System.Col ...

  8. eclipse下出现奇怪字符的解决方法

    eclipse在代码编辑界面出现了奇怪的字符,如下图: 其中包括:换行符,制表符等. 解决方法如下: 点击工具栏的显示空格字符即可.

  9. java集合类中的迭代器模式

    不说模式的问题,看一个<<设计模式之禅>>里面的例子. 老板要看到公司了各个项目的情况.(我知道我这个概述很让人头大,看代码吧) 示例程序 v1 package Iterato ...

  10. HBase丢失数据的故障和原因分析

    hbase的稳定性是近期社区的重要关注点,毕竟稳定的系统才能被推广开来,这里有几次稳定性故障和大家分享.     第一次生产故障的现象及原因     现象: 1 hbase发现无法写入 2 通过hbc ...