设计模式精讲(十八):模板方法模式 - 定义算法骨架
今天聊聊模板方法模式。这是最简单但最常用的设计模式之一,核心思想是:在父类定义算法骨架,子类实现具体步骤。
什么是模板方法模式?
模板方法模式定义一个操作中的算法骨架,将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
生活中的例子:考试答题。题目(模板)固定,每个学生(子类)的答案不同。但答题流程是一样的:读题→思考→作答→检查。
实际场景:数据导出
// 抽象类:定义算法骨架
public abstract class DataExporter
{
// 模板方法:定义算法骨架(final,不允许子类覆盖)
public void Export(string fileName)
{
Console.WriteLine($"\n=== 开始导出数据 ===");
// 1. 打开文件
var stream = OpenFile(fileName);
// 2. 写入头部
WriteHeader(stream);
// 3. 获取数据
var data = GetData();
// 4. 写入数据(可选步骤,子类可以不实现)
if (ShouldWriteData())
{
WriteData(stream, data);
}
// 5. 写入尾部
WriteFooter(stream);
// 6. 关闭文件
CloseFile(stream);
Console.WriteLine($"=== 导出完成 ===\n");
}
// 基本方法:子类必须实现
protected abstract Stream OpenFile(string fileName);
protected abstract void WriteHeader(Stream stream);
protected abstract void WriteData(Stream stream, List<string[]> data);
protected abstract void WriteFooter(Stream stream);
protected abstract void CloseFile(Stream stream);
// 钩子方法:子类可以选择实现
protected virtual bool ShouldWriteData() => true;
// 具体方法:所有子类共享
protected virtual List<string[]> GetData()
{
Console.WriteLine(" 📊 获取数据...");
return new List<string[]>
{
new[] { "张三", "28", "北京" },
new[] { "李四", "32", "上海" },
new[] { "王五", "25", "广州" }
};
}
}
// 具体实现:CSV导出
public class CsvExporter : DataExporter
{
private StreamWriter _writer;
protected override Stream OpenFile(string fileName)
{
Console.WriteLine($" 📄 打开CSV文件: {fileName}");
var stream = new FileStream(fileName, FileMode.Create);
_writer = new StreamWriter(stream);
return stream;
}
protected override void WriteHeader(Stream stream)
{
Console.WriteLine(" 📝 写入CSV表头");
_writer.WriteLine("姓名,年龄,城市");
}
protected override void WriteData(Stream stream, List<string[]> data)
{
Console.WriteLine($" 📝 写入{data.Count}行数据");
foreach (var row in data)
{
_writer.WriteLine(string.Join(",", row));
}
}
protected override void WriteFooter(Stream stream)
{
Console.WriteLine(" 📝 CSV无需尾部");
}
protected override void CloseFile(Stream stream)
{
Console.WriteLine(" ✅ 关闭CSV文件");
_writer?.Flush();
stream?.Close();
}
}
// 具体实现:JSON导出
public class JsonExporter : DataExporter
{
private StreamWriter _writer;
protected override Stream OpenFile(string fileName)
{
Console.WriteLine($" 📄 打开JSON文件: {fileName}");
var stream = new FileStream(fileName, FileMode.Create);
_writer = new StreamWriter(stream);
return stream;
}
protected override void WriteHeader(Stream stream)
{
Console.WriteLine(" 📝 写入JSON起始");
_writer.WriteLine("[");
}
protected override void WriteData(Stream stream, List<string[]> data)
{
Console.WriteLine($" 📝 写入JSON数据");
for (int i = 0; i < data.Count; i++)
{
var row = data[i];
var comma = i < data.Count - 1 ? "," : "";
_writer.WriteLine($" {{\"name\": \"{row[0]}\", \"age\": {row[1]}, \"city\": \"{row[2]}\"}}{comma}");
}
}
protected override void WriteFooter(Stream stream)
{
Console.WriteLine(" 📝 写入JSON结束");
_writer.WriteLine("]");
}
protected override void CloseFile(Stream stream)
{
Console.WriteLine(" ✅ 关闭JSON文件");
_writer?.Flush();
stream?.Close();
}
}
// 具体实现:XML导出
public class XmlExporter : DataExporter
{
private StreamWriter _writer;
protected override Stream OpenFile(string fileName)
{
Console.WriteLine($" 📄 打开XML文件: {fileName}");
var stream = new FileStream(fileName, FileMode.Create);
_writer = new StreamWriter(stream);
return stream;
}
protected override void WriteHeader(Stream stream)
{
Console.WriteLine(" 📝 写入XML头部");
_writer.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
_writer.WriteLine("<users>");
}
protected override void WriteData(Stream stream, List<string[]> data)
{
Console.WriteLine($" 📝 写入XML数据");
foreach (var row in data)
{
_writer.WriteLine(" <user>");
_writer.WriteLine($" <name>{row[0]}</name>");
_writer.WriteLine($" <age>{row[1]}</age>");
_writer.WriteLine($" <city>{row[2]}</city>");
_writer.WriteLine(" </user>");
}
}
protected override void WriteFooter(Stream stream)
{
Console.WriteLine(" 📝 写入XML尾部");
_writer.WriteLine("</users>");
}
protected override void CloseFile(Stream stream)
{
Console.WriteLine(" ✅ 关闭XML文件");
_writer?.Flush();
stream?.Close();
}
}
// 使用示例
public class Program
{
public static void Main()
{
// CSV导出
var csvExporter = new CsvExporter();
csvExporter.Export("data.csv");
// JSON导出
var jsonExporter = new JsonExporter();
jsonExporter.Export("data.json");
// XML导出
var xmlExporter = new XmlExporter();
xmlExporter.Export("data.xml");
}
}
更实际的例子:银行账户操作
// 抽象类:银行账户
public abstract class BankAccount
{
public string AccountNumber { get; }
public decimal Balance { get; protected set; }
protected BankAccount(string accountNumber, decimal initialBalance)
{
AccountNumber = accountNumber;
Balance = initialBalance;
}
// 模板方法:存款流程
public void Deposit(decimal amount)
{
Console.WriteLine($"\n=== 存款操作 ===");
Console.WriteLine($"账户: {AccountNumber}");
Console.WriteLine($"存入金额: ¥{amount:F2}");
// 1. 验证金额
ValidateAmount(amount);
// 2. 计算利息(可选,储蓄账户有)
var interest = CalculateDepositInterest(amount);
// 3. 更新余额
Balance += amount + interest;
Console.WriteLine($"当前余额: ¥{Balance:F2}");
// 4. 记录交易
RecordTransaction("存款", amount);
// 5. 发送通知(可选)
SendNotification("存款", amount);
}
// 模板方法:取款流程
public bool Withdraw(decimal amount)
{
Console.WriteLine($"\n=== 取款操作 ===");
Console.WriteLine($"账户: {AccountNumber}");
Console.WriteLine($"取款金额: ¥{amount:F2}");
// 1. 验证金额
ValidateAmount(amount);
// 2. 检查余额
if (!CheckBalance(amount))
{
Console.WriteLine("❌ 余额不足");
return false;
}
// 3. 计算手续费(不同账户不同)
var fee = CalculateWithdrawalFee(amount);
Console.WriteLine($"手续费: ¥{fee:F2}");
// 4. 更新余额
Balance -= (amount + fee);
Console.WriteLine($"当前余额: ¥{Balance:F2}");
// 5. 记录交易
RecordTransaction("取款", amount);
// 6. 发送通知
SendNotification("取款", amount);
return true;
}
// 基本方法:所有账户相同
protected virtual void ValidateAmount(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("金额必须大于0");
Console.WriteLine("✅ 金额验证通过");
}
// 基本方法:所有账户相同
protected virtual bool CheckBalance(decimal amount)
{
return Balance >= amount;
}
// 抽象方法:子类必须实现
protected abstract decimal CalculateWithdrawalFee(decimal amount);
protected abstract decimal CalculateDepositInterest(decimal amount);
// 钩子方法:子类可选实现
protected virtual void RecordTransaction(string type, decimal amount)
{
Console.WriteLine($" 📝 记录交易: {type} ¥{amount:F2}");
}
protected virtual void SendNotification(string type, decimal amount)
{
// 默认不发送通知
}
}
// 具体实现:储蓄账户
public class SavingsAccount : BankAccount
{
private readonly decimal _interestRate;
public SavingsAccount(string accountNumber, decimal initialBalance, decimal interestRate = 0.03m)
: base(accountNumber, initialBalance)
{
_interestRate = interestRate;
}
protected override decimal CalculateWithdrawalFee(decimal amount)
{
return 0; // 储蓄账户无手续费
}
protected override decimal CalculateDepositInterest(decimal amount)
{
return amount * _interestRate; // 有存款利息
}
protected override void SendNotification(string type, decimal amount)
{
Console.WriteLine($" 📱 发送短信通知: {type} ¥{amount:F2}");
}
}
// 具体实现:信用卡账户
public class CreditCardAccount : BankAccount
{
private readonly decimal _withdrawalFeeRate;
public CreditCardAccount(string accountNumber, decimal creditLimit, decimal withdrawalFeeRate = 0.01m)
: base(accountNumber, creditLimit)
{
_withdrawalFeeRate = withdrawalFeeRate;
}
protected override decimal CalculateWithdrawalFee(decimal amount)
{
return amount * _withdrawalFeeRate; // 取现有手续费
}
protected override decimal CalculateDepositInterest(decimal amount)
{
return 0; // 信用卡无存款利息
}
protected override bool CheckBalance(decimal amount)
{
return true; // 信用卡可以透支
}
}
// 使用示例
public class Program
{
public static void Main()
{
// 储蓄账户
var savings = new SavingsAccount("SA001", 10000);
savings.Deposit(1000);
savings.Withdraw(500);
// 信用卡账户
var creditCard = new CreditCardAccount("CC001", 50000);
creditCard.Deposit(5000);
creditCard.Withdraw(10000); // 可以透支
}
}
模板方法中的方法分类
1. 抽象方法
// 子类必须实现
protected abstract void DoSomething();
2. 具体方法
// 子类直接继承使用
protected void DoSomethingCommon() { ... }
3. 钩子方法
// 子类可以选择覆盖
protected virtual bool ShouldDoSomething() => true;
4. 模板方法
// 定义算法骨架,不允许子类覆盖
public void TemplateMethod()
{
Step1();
if (HookMethod())
{
Step2();
}
Step3();
}
优点
1. 代码复用
// 公共逻辑在父类,避免重复
// 子类只需实现差异部分
2. 控制扩展点
// 父类控制算法结构
// 子类只能在指定点扩展
3. 符合开闭原则
// 新增导出格式只需新增子类
// 不影响现有代码
缺点
1. 类数量增加
// 每种实现需要一个子类
2. 继承的限制
// Java/C#不支持多继承
// 一个类只能有一个父类
3. 子类影响父类
// 如果父类修改模板方法
// 所有子类都可能受影响
适用场景
✅ 推荐使用
算法骨架固定,步骤可变
数据导出 报表生成 业务流程
代码复用
多个类有相同逻辑 只有部分步骤不同
❌ 不推荐使用
算法步骤差异大
不同实现差异太多
需要灵活组合
策略模式更合适
实际项目中的应用
1. ASP.NET Core 中间件
public abstract class Middleware
{
protected readonly RequestDelegate _next;
protected Middleware(RequestDelegate next)
{
_next = next;
}
// 模板方法
public async Task InvokeAsync(HttpContext context)
{
// 前置处理
await BeforeInvoke(context);
// 调用下一个中间件
await _next(context);
// 后置处理
await AfterInvoke(context);
}
protected virtual Task BeforeInvoke(HttpContext context) => Task.CompletedTask;
protected virtual Task AfterInvoke(HttpContext context) => Task.CompletedTask;
}
2. 单元测试基类
public abstract class TestBase
{
// 模板方法
[TestInitialize]
public void Setup()
{
SetupTestData();
SetupMocks();
SetupService();
}
protected abstract void SetupTestData();
protected abstract void SetupMocks();
protected virtual void SetupService() { }
}
最佳实践
1. 模板方法设为final/sealed
// C#中没有sealed方法,但可以通过不标记virtual实现
public void Export() // 没有virtual,子类无法覆盖
{
// ...
}
2. 合理使用钩子方法
// 提供默认实现,子类可选覆盖
protected virtual bool ShouldLog() => true;
protected virtual void OnCompleted() { }
3. 使用protected暴露扩展点
// public给客户端用
// protected给子类扩展
public void Process() { ... }
protected abstract void DoProcess();
总结
模板方法模式是"骨架+填充"的艺术,核心思想是:父类定义算法骨架,子类实现具体步骤。
记住这个公式:
模板方法 = 算法骨架(不可变) 抽象方法 = 必须实现的步骤 钩子方法 = 可选实现的步骤
选型建议:
算法骨架固定?用模板方法 多个子类有相同逻辑?用模板方法 需要控制扩展点?用模板方法
下一期,我们聊聊迭代器模式——如何遍历集合而不暴露其内部结构。敬请期待!
💡 思考题:模板方法模式和策略模式都可以实现算法的变化,它们有什么区别?什么时候用模板方法,什么时候用策略?
夜雨聆风