shanzhiming
shanzm-2020年7月20日 01:55:50

1. 简介

1.定义

模版方法模式(Template Method Pattern):用于定义一个操作中算法的骨架,而将一些步骤延迟到子类中。

模版方法模式使得子类可以不改变一个算法的结构及可重定义该算法的某些特定步骤。

简而言之:模版方法模式功能在于固定算法骨架,而让具体算法实现可扩展。

其实只要是理解继承和多态即可以立即明白模版方法模式。模版方法模式是通过把不变行为搬移到父类,除去子类中的重复代码。

2.主要类

  • AbstractClass:抽象类。用来定义算法骨架(抽象方法)。具体的子类通过重写抽象方法来实现一个算法的各个步骤。

  • ConcreteClass:具体实现类。用来实现算法骨架中的某些步骤,完成与特定子类相关的功能。

3.名词解释

  • 模版方法:抽象类中定义算法骨架的方法。

  • 原语操作:即抽象方法,抽象类中定义的抽象操作。

  • 具体操作:即具体方法,抽象类定义的具体方法体的方法

  • 钩子方法:用于决定模版方法中某个方法是否执行的方法。钩子方法就是通过子类的行为去反向控制父类的行为的一种方法

4.抽象工厂模式的UML

Template Method Pattern UML

注:原图片来自《设计模式实训教程-第二版》

2. 示例

2.1 背景说明

对数据库的操作:连接-->打开-->使用-->关闭

对于不同的数据操作步骤都是一样的,只是连接数据库稍有不同

这里我们定义一个抽象模版类DBOperator,其中我们定义ConnDB()、OpenDB()、UseDB()、CloseDB()四个抽象方法

定义不同的数据库具体实现类:SQLServerDBOperator、OracleDBOperator

2.2 代码实现

①定义抽象父类

public abstract class DBOperator
{
    //原语操作1
    public abstract void ConnDB();
    //原语操作2
    public void OpenDB()
    {
        Console.WriteLine("打开数据库");
    }
    //原语操作3
    public void UseDB()
    {
        Console.WriteLine("使用数据库");
    }
    //原语操作4
    public void CloseDB()
    {
        Console.WriteLine("关闭数据库");
    }

    //钩子方法:用于子类反向控制父类中某个方法是否执行
    //注意钩子方法这里定义为虚方法,虚方法和抽象方法不同的地方就是虚方法有方法体,这样我们就可以在虚方法中定义默的方法
    public virtual bool IsStart()
    {
        return true;//默认为true所以在具体子类中重写
    }

    //模版方法:定义算法骨架
    //确定其他基本方法的执行顺序
    public void Process()
    {
        ConnDB();
        OpenDB();
        if (IsStart())//通过钩子方法控制是否执行UseDB()
        {
            UseDB();
        }
        CloseDB();
    }
}

②定义具体实现类

 //具体子类1
public class OracleDBOperator : DBOperator
{
    public override void ConnDB()
    {
        Console.WriteLine("连接Oracle数据库");
    }
    //按照面向对象的思想,我们可以在子类中使用new 关键字覆盖父类中的方法
    //注意:
    //1. 这里覆盖了UseDB(),是不够的,还要覆盖调用这个方法的方法Process()
    //2. 若是父类引用指向子类对象,则需要将父类转化为子类对象才可以使用子类中覆盖的方法
    public new void UseDB()
    {
        Console.WriteLine("使用Oracle数据库");
    }

    public new void Process()
    {
        ConnDB();
        OpenDB();
        if (IsStart())
        {
            UseDB();
        }
        CloseDB();
    }
}

//具体子类2
public class SQLServerDBOperator : DBOperator
{
    ///具体子类继承抽象父类,重写抽象父类中的抽象方法
    ///抽象父类中的非抽象方法就是每个子类都通用的方法,
    public override void ConnDB()
    {
        Console.WriteLine("连接SQL Server数据库");
    }

    public override bool IsStart()//覆盖了钩子函数,修改了抽象父类中模版方法,实现子类反向控制父类
    {
        return false;
    }
}

③客户端

static void Main(string[] args)
{
     static void Main(string[] args)
        {
            DBOperator sqlServerDBOperator = new SQLServerDBOperator();
            DBOperator oracleDBOperator = new OracleDBOperator();

            sqlServerDBOperator.Process(); //注意这里,我们定义的钩子方法是虚方法,通过重写,实现子类控制父类,这里是不需要将强转为子类对象
            Console.WriteLine("---------------------------");

            oracleDBOperator.Process();
            Console.WriteLine("---------------------------");

            ((OracleDBOperator)oracleDBOperator).Process(); //这里是通过覆盖父类中的方法,所以需要将父类强转为相应的子类对象
            Console.WriteLine("---------------------------");

            Console.ReadKey();
        }
}

④运行结果

连接SQL Server数据库
打开数据库
关闭数据库
---------------------------
连接Oracle数据库
打开数据库
使用数据库
关闭数据库
---------------------------
连接Oracle数据库
打开数据库
使用Oracle数据库
关闭数据库


3. 总结分析

3.1 补充

1.模版方法模式中为何使用的是抽象类而不是接口?

这里先说明一下接口和抽象类的区别:

  • 接口只是定义一组行为,某个类(非抽象类)实现这个接口就需要使用该接口中的所有方法。简而言之:接口约束实现该接口的类的行为。所以接口也称之为契约。

  • 抽象类中的抽象方法需要子类重写,而抽象类可以有非抽象方法,非抽象方法是需要在抽象类中写方法体的。简而言之:抽象类不仅约束子类的行为而且需要为子类中非抽象方法提供方法体

这里我们使用抽象类而不是使用接口,为什么呢?若是定义为抽象方法则我们需要在实现类中需要对抽象方法都重写,而可能多个子类中的某个方法实现是一样的。

这里也体现了接口和抽象类的不同之处:接口约束实现接口的类行为(所以接口也称之为契约)而抽象类不仅要约束子类的行为,而要为子类提供公共方法体

这里示例中的数据库连接函数ConnDB定义为抽象方法,因为每个数据库的连接方法是不一样的,所以在每个具体的实现类中都需要对其重写 。而其他的数据库操作方法类似,所以我们在父类中对其实现,避免子类中代码冗余。

3.2 优点

模板方法模式的优点是实现代码复用。
模板方法模式是一种实现代码复用的很好的手段。通过把子类的公共功能提炼和抽取,把公共部分放到模板中去实现。

模版方法模式体现类开闭原则。首先模版方法模式从设计上分离变与不变,然后把不变的部分抽取出来,定义到父类中,比如算法骨架,一些公共的、固定的实现等。这些不变的部分被封闭起来,尽量不去修改它们。要想扩展新的功能,那就使用子类来扩展,通过子类来实现可变化的步骤,对于这种新增功能的做法是开放的。

3.3 缺点

模板方法模式的缺点是算法骨架不容易升级。
模板方法模式最基本的功能就是通过模板的制定,把算法骨架完全固定下来。事实上模板和子类是非常耦合的,如果要对模板中的算法骨架进行变更,可能就会要求所有相关的子类进行相应的变化。比如模版方法中添加一个抽象方法,则所有的子类都需要去实现这方法。
所以抽取算法骨架的时候要特别小心,尽量确保是不会变化的部分才放到模板中。

3.4 适应场合

建议在以下情况中选用模板方法模式

  • 需要固定定义算法骨架。实现一个算法的不变的部分,并把可变的行为留给子类来实现的情况。
  • 各个子类中具有公共行为,应该抽取出来,集中在一个公共类中去实现,从而避免代码重复。
  • 需要控制子类扩展的情况。模板方法模式会在特定的点来调用子类的方法,这样只允许在这些点进行扩展。

3.5 对比与合用

【TODO】(待策略模式研究完在完成此部分!)

  • 模版方法模式与工厂方法模式

  • 模版方法模式和策略模式


4. 参考及源码

相关文章: