设计模式复习(十二)-------模板方法模式

1. 模板方法的引入

变与不变

  • 变化是软件设计的永恒主题,如何管理变化带来的复杂性?设计模式的艺术性和复杂度就在于如何分析,并发现系统中的变化点和稳定点,并使用特定的设计方法来应对这种变化。
  • 在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。
  • 如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
  • 准备一个抽象类,将部分逻辑以具体方法以及具体构造子类的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模版方法模式的用意。

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构,即可重定义该算法的某些特定步骤。
——《设计模式》GoF

2. 模板方法的结构

image-20200702153227575
  • 抽象模版(AbstractClass)角色:
    • 定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
    • 定义并实现了一个模版方法。这个模版方法一般是一个具体方法,它给出了一个顶级逻辑的骨架(操作步骤),而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
  • 具体模版(ConcreteClass)角色:
    • 实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
    • 每一个抽象模版角色都可以有任意多个具体模版角色与之对应,而每一个具体模版角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。

模板方法模式的实现

  • 模板方法 (Template Method)—实现整体操作
  • 基本方法 (Primitive Method)—实现基本操作步骤
  • 抽象方法(Abstract Method)
    父类中声明,必须在子类中实现
  • 具体方法(Concrete Method)
    父类中给出实现,子类可以覆盖,也可以直接继承使用
  • 钩子方法(Hook Method)
    • 与“具体步骤”挂钩,子类可以覆盖,返回值为bool
    • 空方法,子类覆盖,实现IOC

3. 模板方法模式的代码实现

钩子方法

……
//模板方法
public void TemplateMethod() 
{
    Open();
    Display();
    //通过钩子方法来确定某步骤是否执行
    if (IsPrint()) 
    {
        Print();
    }
}

//钩子方法—返回bool,可以在模板方法中起到选择的作用,子类可以重写
public bool IsPrint() 
{
    return true;  //如果不需要执行Print(),则可以重写此方法,并返回false
}
……

抽象类典型代码:

abstract class AbstractClass 
{
    //模板方法
    public void TemplateMethod() 
    {
            PrimitiveOperation1();
            PrimitiveOperation2();
            PrimitiveOperation3();
    }
    //基本方法—具体方法,一般不需要子类重写
    public void PrimitiveOperation1() 
    {
        //实现代码
    }
    //基本方法—抽象方法—子类中必须重写
    public abstract void PrimitiveOperation2();    
    //基本方法—钩子方法(空方法)—子类中可以重写—不重写的话则不执行该功能
    public virtual void PrimitiveOperation3()   
    {  }
}

具体子类典型代码:

class ConcreteClass : AbstractClass 
{
    public override void PrimitiveOperation2() 
    {
        //实现代码
    }
    public override void PrimitiveOperation3() 
    {
        //实现代码
    }
}

4. 模版方法模式使用原则

  • 将行为尽量移动到结构的高端(抽象类),而将状态尽量移动到结构的低端(具体类)。
    应当根据行为而不是状态定义一个类。
    • 也就是说,一个类的实现首先建立在行为的基础之上,而不是建立在状态的基础之上。
      在实现行为时,尽量使用抽象状态而不是用具体状态。
    • 如果一个行为涉及到对象的状态时,使用间接的引用而不是直接的引用,换言之,应当使用取值方法而不是直接引用属性。
      给操作划分层次。
    • 一个类的行为(反映该类功能/职责的核心方法)应当放到一组核心方法(Kernel Methods)里面,这些方法可以很方便地在子类中加以置换–override。
      将状态属性的确认推迟到子类中。
    • 不要在抽象类中过早地声明属性变量,应将它们尽量地推迟到子类中去声明。在抽象父类中,如果需要状态属性的话,可以调用抽象的取值方法,而将抽象的取值方法的实现放到具体子类中–状态变量及其存取方法都应该尽量放到子类中。

5. 模板方法模式的总结

  • Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(虚函数的多态性)为很多应用程序框架提供了灵活的扩展,是代码复用方面的基本实现结构。
  • “Don‘t call me, let me call you(不要调用我,让我来调用你)”的反向控制结构(IoC)是Template Method的典型应用。
  • 通过将不变行为移到父类中,去除子类中的重复代码,从而达到代码复用的目的。

6.例子

在这里插入图片描述

namespace TemplateMethodSample{
    // 模板类
    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(); //具体方法
	    }
    }
}

namespace TemplateMethodSample{
    // 活期账户类--具体子类
    class CurrentAccount : Account{
        //覆盖父类的抽象基本方法
        public override void CalculateInterest() {
		    Console.WriteLine("按活期利率计算利息!");
	    }
    }
}

namespace TemplateMethodSample{
    // 定期账户类--具体子类
    class SavingAccount : Account{
        //覆盖父类的抽象基本方法
        public override void CalculateInterest() {
		    Console.WriteLine("按定期利率计算利息!");
	    }
    }
}

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="subClass" value="TemplateMethodSample.CurrentAccount"/>
  </appSettings>
</configuration>
namespace TemplateMethodSample{
    class Program{
        static void Main(string[] args){
            Account account;
            //读取配置文件
            string subClassStr = ConfigurationManager.AppSettings["subClass"];
            //反射生成对象
            account = (Account)Assembly.Load("TemplateMethodSample").CreateInstance(subClassStr);
            account.Handle("张无忌", "123456"); //调用模板方法
        }
    }
}

在这里插入图片描述

namespace TemplateMethodSample{
    // 数据图表显示类--抽象模板类
    abstract class DataViewer{
        //抽象方法:获取数据
        public abstract void GetData();

        //具体方法:转换数据--这里试图将所有不同格式,统一转换为xml格式--也可以在子类中实现格式转换
        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();
        }
    }
}

namespace TemplateMethodSample
{
    /// <summary>
    /// 其他格式--具体模板类
    /// </summary>
    class OtherDataViewer : DataViewer
    {
        //实现父类方法:获取数据
        public override void GetData()
        {
            Console.WriteLine("从其他格式文件中获取数据。");
        }

        //实现父类方法:显示数据
        public override void DisplayData()
        {
            Console.WriteLine("以饼状图显示数据。"); 
        }
    }
}

namespace TemplateMethodSample
{
    /// <summary>
    /// XML格式--具体模板类
    /// </summary>
    class XMLDataViewer : DataViewer
    {
        //实现父类方法:获取数据
        public override void GetData() 
        {
		    Console.WriteLine("从XML文件中获取数据。");
	    }

        //实现父类方法:显示数据
        public override void DisplayData() 
        {
            Console.WriteLine("以柱状图显示数据。");
	    }

        //覆盖父类的钩子方法--表明不需要格式转换
        public override bool IsNotXMLData()
        {
            return false;
        }
    }
}

namespace TemplateMethodSample
{
    class Program
    {
        static void Main(string[] args)
        {
            DataViewer dv;
            //dv = new XMLDataViewer();
            dv = new OtherDataViewer();
            dv.Process();
            
            Console.Read();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值