模板模式是一种行为设计模式,它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以不改变算法结构的情况下,重新定义算法中的某些特定步骤。

模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。

在模板模式经典的实现中,模板方法定义为 final(java)或者 sealed(c#),可以避免被子类重写。需要子类重写的方法定义为 abstract,可以强迫子类去实现。不过,在实际项目开发中,模板模式的实现比较灵活,以上两点都不是必须的。

模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。

1. 核心概念

模板模式包含两个主要部分:

  1. 抽象类 (Abstract Class)​​:

    • 定义算法骨架(模板方法)

    • 包含基本方法(抽象方法、具体方法和钩子方法)

  2. 具体子类 (Concrete Class)​​:

    • 实现抽象类中的抽象方法

    • 可以覆盖钩子方法

2. 模式结构

classDiagram
    class AbstractClass {
        <<abstract>>
        +TemplateMethod() void
        +PrimitiveOperation1()* void
        +PrimitiveOperation2()* void
        +Hook() void
    }
    
    class ConcreteClassA {
        +PrimitiveOperation1() void
        +PrimitiveOperation2() void
    }
    
    class ConcreteClassB {
        +PrimitiveOperation1() void
        +PrimitiveOperation2() void
        +Hook() void
    }
    
    AbstractClass <|-- ConcreteClassA
    AbstractClass <|-- ConcreteClassB

3. 代码示例

3.1 基本实现

// 抽象类 - 定义模板方法
public abstract class DataProcessor
{
    // 模板方法 - 定义算法骨架
    public void ProcessData()
    {
        ReadData();
        ProcessDataCore();
        if (NeedValidate())
        {
            ValidateData();
        }
        SaveResult();
    }
    
    // 抽象方法 - 必须由子类实现
    protected abstract void ProcessDataCore();
    
    // 具体方法 - 已有默认实现
    protected virtual void ReadData()
    {
        Console.WriteLine("从默认数据源读取数据...");
    }
    
    // 具体方法 - 已有默认实现
    protected virtual void SaveResult()
    {
        Console.WriteLine("将结果保存到默认位置...");
    }
    
    // 钩子方法 - 可选覆盖
    protected virtual bool NeedValidate()
    {
        return false;
    }
    
    // 钩子方法 - 可选覆盖
    protected virtual void ValidateData()
    {
        Console.WriteLine("执行默认数据验证...");
    }
}

// 具体子类 - CSV数据处理
public class CsvDataProcessor : DataProcessor
{
    protected override void ProcessDataCore()
    {
        Console.WriteLine("处理CSV格式数据...");
    }
    
    protected override void ReadData()
    {
        Console.WriteLine("从CSV文件读取数据...");
    }
    
    protected override bool NeedValidate()
    {
        return true;
    }
}

// 具体子类 - XML数据处理
public class XmlDataProcessor : DataProcessor
{
    protected override void ProcessDataCore()
    {
        Console.WriteLine("处理XML格式数据...");
    }
    
    protected override void SaveResult()
    {
        Console.WriteLine("将结果保存为XML格式...");
    }
}

// 客户端代码
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("处理CSV数据:");
        DataProcessor csvProcessor = new CsvDataProcessor();
        csvProcessor.ProcessData();
        
        Console.WriteLine("\n处理XML数据:");
        DataProcessor xmlProcessor = new XmlDataProcessor();
        xmlProcessor.ProcessData();
    }
}

3.2 更复杂的例子 - 游戏框架

// 游戏框架中的模板模式应用
public abstract class Game
{
    // 模板方法 - 定义游戏流程
    public void Play()
    {
        Initialize();
        StartGame();
        while (!IsGameOver())
        {
            TakeTurn();
        }
        EndGame();
        DisplayWinner();
    }
    
    protected abstract void Initialize();
    protected abstract void StartGame();
    protected abstract void TakeTurn();
    protected abstract bool IsGameOver();
    protected abstract void DisplayWinner();
    
    // 钩子方法 - 可选实现
    protected virtual void EndGame()
    {
        Console.WriteLine("游戏结束!");
    }
}

// 象棋游戏实现
public class Chess : Game
{
    private int _turn = 1;
    private int _maxTurns = 10;
    
    protected override void Initialize()
    {
        Console.WriteLine("设置棋盘,摆放棋子...");
    }
    
    protected override void StartGame()
    {
        Console.WriteLine("象棋游戏开始! 红方先行。");
    }
    
    protected override void TakeTurn()
    {
        Console.WriteLine($"回合 {_turn}: {( _turn % 2 == 1 ? "红方" : "黑方" )}走棋...");
        _turn++;
    }
    
    protected override bool IsGameOver()
    {
        return _turn > _maxTurns;
    }
    
    protected override void DisplayWinner()
    {
        Console.WriteLine("平局! 双方势均力敌。");
    }
    
    protected override void EndGame()
    {
        Console.WriteLine("象棋游戏结束,清理棋盘...");
    }
}

// 扑克游戏实现
public class Poker : Game
{
    protected override void Initialize()
    {
        Console.WriteLine("洗牌,发牌...");
    }
    
    protected override void StartGame()
    {
        Console.WriteLine("德州扑克开始! 下盲注。");
    }
    
    protected override void TakeTurn()
    {
        Console.WriteLine("新一轮下注...");
    }
    
    protected override bool IsGameOver()
    {
        Console.WriteLine("有玩家全押了吗? (y/n)");
        return Console.ReadLine().ToLower() == "y";
    }
    
    protected override void DisplayWinner()
    {
        Console.WriteLine("恭喜玩家X赢得比赛!");
    }
}

// 客户端代码
class GameClient
{
    static void Main()
    {
        Console.WriteLine("玩象棋:");
        Game chess = new Chess();
        chess.Play();
        
        Console.WriteLine("\n玩扑克:");
        Game poker = new Poker();
        poker.Play();
    }
}

作者实现案例:


namespace DesignPatternsPractice.Src.Behavioral.TemplateMethodDesign2
{
    /// <summary>
    /// 模板模式 
    /// 普通的运算场景
    /// </summary>
    public class TemplateMethod2
    {



        static void Main()
        {
            OperationNum operationNum = new OperationNum(10, 20);
            operationNum.operational();
            operationNum.thread.Start();
        }
    }

    public abstract class Operation
    {
        public abstract int Num1();
        public abstract int Num2();
        /// <summary>
        /// 所有的运算
        /// </summary>
        public void operational()
        {
            // Console.WriteLine("加法结果为:" + (Num1() + Num2()));
            // Console.WriteLine("减法结果为:" + (Num1() - Num2()));
            // Console.WriteLine("乘法结果为:" + (Num1() * Num2()));
            // if (Num2() != 0)
            // {
            //     Console.WriteLine("除法结果为:" + (Num1() / Num2()));
            // }
            // else
            // {
            //     Console.WriteLine("除法结果为:除数不能为0");
            // }
        }
    }

    public class OperationNum : Operation
    {
        public Thread thread;
        private int _num1;
        private int _num2;

        public OperationNum(int num1, int num2)
        {
            _num1 = num1;
            _num2 = num2;
            // 线程练习
            thread = new Thread(() =>
            {
                Console.WriteLine("启动了一个线程!");
            });
        }
        public override int Num1()
        {
            return _num1;
        }

        public override int Num2()
        {
            return _num2;
        }
    }
}

4. 模板模式的优点

  1. 代码复用​:将公共代码放在抽象类中,避免重复

  2. 扩展性好​:通过子类扩展具体行为,符合开闭原则

  3. 控制反转​:父类控制流程,子类实现细节

  4. 提高可维护性​:算法结构清晰,易于理解和维护

  5. 灵活性​:通过钩子方法提供额外扩展点

5. 适用场景

  1. 多个类有相同算法结构,但某些步骤实现不同时

  2. 需要控制子类扩展点时

  3. 框架设计,定义流程骨架,允许用户自定义部分步骤

  4. 需要固定算法执行顺序时

  5. 重构时提取公共行为到父类

6. 实际应用案例

  1. 框架设计​:如Spring框架的JdbcTemplate

  2. GUI框架​:如Windows Forms的控件绘制流程

  3. 游戏开发​:游戏主循环的实现

  4. 数据处理流程​:ETL(Extract-Transform-Load)过程

  5. 编译器设计​:编译流程的各个阶段

  6. 测试框架​:测试用例的执行流程

7. 注意事项

  1. 控制子类扩展​:模板方法应尽量减少可覆盖的方法数量

  2. 避免过度抽象​:不是所有重复代码都适合用模板模式

  3. 命名约定​:模板方法通常命名为"DoOperation"或"PerformOperation"

  4. 好莱坞原则​:"不要调用我们,我们会调用你" - 子类不应直接调用模板方法

模板模式是一种强大的设计模式,特别适合定义算法骨架并允许部分步骤灵活变化的场景。正确使用它可以显著提高代码的复用性和可维护性。