模板模式是一种行为设计模式,它定义了一个操作中的算法骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以不改变算法结构的情况下,重新定义算法中的某些特定步骤。
模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。这里的“算法”,我们可以理解为广义上的“业务逻辑”,并不特指数据结构和算法中的“算法”。这里的算法骨架就是“模板”,包含算法骨架的方法就是“模板方法”,这也是模板方法模式名字的由来。
在模板模式经典的实现中,模板方法定义为 final(java)或者 sealed(c#)
,可以避免被子类重写。需要子类重写的方法定义为 abstract,可以强迫子类去实现。不过,在实际项目开发中,模板模式的实现比较灵活,以上两点都不是必须的。
模板模式有两大作用:复用和扩展。其中,复用指的是,所有的子类可以复用父类中提供的模板方法的代码。扩展指的是,框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能。
1. 核心概念
模板模式包含两个主要部分:
抽象类 (Abstract Class):
定义算法骨架(模板方法)
包含基本方法(抽象方法、具体方法和钩子方法)
具体子类 (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. 模板模式的优点
代码复用:将公共代码放在抽象类中,避免重复
扩展性好:通过子类扩展具体行为,符合开闭原则
控制反转:父类控制流程,子类实现细节
提高可维护性:算法结构清晰,易于理解和维护
灵活性:通过钩子方法提供额外扩展点
5. 适用场景
多个类有相同算法结构,但某些步骤实现不同时
需要控制子类扩展点时
框架设计,定义流程骨架,允许用户自定义部分步骤
需要固定算法执行顺序时
重构时提取公共行为到父类
6. 实际应用案例
框架设计:如Spring框架的JdbcTemplate
GUI框架:如Windows Forms的控件绘制流程
游戏开发:游戏主循环的实现
数据处理流程:ETL(Extract-Transform-Load)过程
编译器设计:编译流程的各个阶段
测试框架:测试用例的执行流程
7. 注意事项
控制子类扩展:模板方法应尽量减少可覆盖的方法数量
避免过度抽象:不是所有重复代码都适合用模板模式
命名约定:模板方法通常命名为"DoOperation"或"PerformOperation"
好莱坞原则:"不要调用我们,我们会调用你" - 子类不应直接调用模板方法
模板模式是一种强大的设计模式,特别适合定义算法骨架并允许部分步骤灵活变化的场景。正确使用它可以显著提高代码的复用性和可维护性。