更实际的例子来解释它(访问者模式)的威力。
访问者模式的真正价值
当前代码的问题分析
代码展示了机制,但没有展示为什么需要这种机制。让我用一个财务报表的例子来演示:
csharp
using System;
using System.Collections.Generic;
// 财务数据元素接口
public interface IFinancialElement
{
void Accept(IFinancialVisitor visitor); // 接受财务访问者
decimal GetAmount(); // 获取金额
}
// 收入项
public class IncomeItem : IFinancialElement
{
public string Description { get; set; } // 描述
public decimal Amount { get; set; } // 金额
public IncomeItem(string description, decimal amount)
{
Description = description;
Amount = amount;
}
public void Accept(IFinancialVisitor visitor)
{
visitor.VisitIncome(this); // 接受访问者访问收入项
}
public decimal GetAmount() => Amount;
}
// 支出项
public class ExpenseItem : IFinancialElement
{
public string Description { get; set; } // 描述
public decimal Amount { get; set; } // 金额
public string Category { get; set; } // 类别
public ExpenseItem(string description, decimal amount, string category)
{
Description = description;
Amount = amount;
Category = category;
}
public void Accept(IFinancialVisitor visitor)
{
visitor.VisitExpense(this); // 接受访问者访问支出项
}
public decimal GetAmount() => Amount;
}
// 资产项
public class AssetItem : IFinancialElement
{
public string Name { get; set; } // 资产名称
public decimal Value { get; set; } // 价值
public decimal Depreciation { get; set; } // 折旧
public AssetItem(string name, decimal value, decimal depreciation)
{
Name = name;
Value = value;
Depreciation = depreciation;
}
public void Accept(IFinancialVisitor visitor)
{
visitor.VisitAsset(this); // 接受访问者访问资产项
}
public decimal GetAmount() => Value;
}
// 财务访问者接口
public interface IFinancialVisitor
{
void VisitIncome(IncomeItem income); // 访问收入
void VisitExpense(ExpenseItem expense); // 访问支出
void VisitAsset(AssetItem asset); // 访问资产
}
// 具体访问者1:财务报表生成器
public class ReportGeneratorVisitor : IFinancialVisitor
{
private decimal _totalIncome = 0; // 总收入
private decimal _totalExpense = 0; // 总支出
private decimal _totalAssets = 0; // 总资产
public void VisitIncome(IncomeItem income)
{
_totalIncome += income.Amount; // 累加收入
Console.WriteLine($"收入: {income.Description} - ¥{income.Amount}"); // 输出收入详情
}
public void VisitExpense(ExpenseItem expense)
{
_totalExpense += expense.Amount; // 累加支出
Console.WriteLine($"支出: {expense.Description} ({expense.Category}) - ¥{expense.Amount}"); // 输出支出详情
}
public void VisitAsset(AssetItem asset)
{
_totalAssets += asset.Value; // 累加资产
Console.WriteLine($"资产: {asset.Name} - 价值: ¥{asset.Value}, 折旧: ¥{asset.Depreciation}"); // 输出资产详情
}
public void PrintReport() // 打印报表
{
Console.WriteLine("\n=== 财务报表 ===");
Console.WriteLine($"总收入: ¥{_totalIncome}"); // 输出总收入
Console.WriteLine($"总支出: ¥{_totalExpense}"); // 输出总支出
Console.WriteLine($"总资产: ¥{_totalAssets}"); // 输出总资产
Console.WriteLine($"净利润: ¥{(_totalIncome - _totalExpense)}"); // 输出净利润
Console.WriteLine($"净资产: ¥{(_totalIncome - _totalExpense + _totalAssets)}"); // 输出净资产
}
}
// 具体访问者2:税务计算器
public class TaxCalculatorVisitor : IFinancialVisitor
{
private decimal _taxableIncome = 0; // 应纳税收入
private decimal _deductibleExpenses = 0; // 可抵扣支出
private decimal _assetDepreciation = 0; // 资产折旧
public void VisitIncome(IncomeItem income)
{
_taxableIncome += income.Amount; // 所有收入都应纳税
Console.WriteLine($"应税收入: {income.Description} - ¥{income.Amount}"); // 输出应税收入
}
public void VisitExpense(ExpenseItem expense)
{
if (expense.Category == "业务" || expense.Category == "办公")
{
_deductibleExpenses += expense.Amount; // 只有业务相关支出可抵扣
Console.WriteLine($"可抵扣支出: {expense.Description} - ¥{expense.Amount}"); // 输出可抵扣支出
}
}
public void VisitAsset(AssetItem asset)
{
_assetDepreciation += asset.Depreciation; // 资产折旧可抵扣
Console.WriteLine($"资产折旧抵扣: {asset.Name} - ¥{asset.Depreciation}"); // 输出折旧抵扣
}
public void PrintTaxReport() // 打印税务报告
{
decimal netTaxableIncome = _taxableIncome - _deductibleExpenses - _assetDepreciation; // 计算净应纳税收入
decimal taxAmount = netTaxableIncome * 0.25m; // 假设税率25%
Console.WriteLine("\n=== 税务报告 ===");
Console.WriteLine($"应纳税收入: ¥{_taxableIncome}"); // 输出应纳税收入
Console.WriteLine($"可抵扣支出: ¥{_deductibleExpenses}"); // 输出可抵扣支出
Console.WriteLine($"资产折旧: ¥{_assetDepreciation}"); // 输出资产折旧
Console.WriteLine($"净应纳税收入: ¥{netTaxableIncome}"); // 输出净应纳税收入
Console.WriteLine($"应缴税款: ¥{taxAmount}"); // 输出应缴税款
}
}
// 具体访问者3:数据导出器
public class DataExporterVisitor : IFinancialVisitor
{
public void VisitIncome(IncomeItem income)
{
// 导出为JSON格式
Console.WriteLine($"{{\"type\": \"income\", \"description\": \"{income.Description}\", \"amount\": {income.Amount}}}"); // JSON格式输出收入
}
public void VisitExpense(ExpenseItem expense)
{
// 导出为JSON格式
Console.WriteLine($"{{\"type\": \"expense\", \"description\": \"{expense.Description}\", \"category\": \"{expense.Category}\", \"amount\": {expense.Amount}}}"); // JSON格式输出支出
}
public void VisitAsset(AssetItem asset)
{
// 导出为JSON格式
Console.WriteLine($"{{\"type\": \"asset\", \"name\": \"{asset.Name}\", \"value\": {asset.Value}, \"depreciation\": {asset.Depreciation}}}"); // JSON格式输出资产
}
}
class Program
{
static void Main()
{
// 创建财务数据
var financialData = new List<IFinancialElement>
{
new IncomeItem("产品销售", 50000),
new IncomeItem("服务收入", 30000),
new ExpenseItem("员工工资", 20000, "人事"),
new ExpenseItem("办公室租金", 8000, "办公"),
new ExpenseItem("业务招待", 3000, "业务"),
new ExpenseItem("个人消费", 5000, "个人"),
new AssetItem("办公设备", 15000, 3000),
new AssetItem("公司车辆", 80000, 16000)
};
Console.WriteLine("=== 财务报表生成 ===");
var reportGenerator = new ReportGeneratorVisitor();
ProcessFinancialData(financialData, reportGenerator); // 处理财务数据生成报表
reportGenerator.PrintReport(); // 打印报表
Console.WriteLine("\n=== 税务计算 ===");
var taxCalculator = new TaxCalculatorVisitor();
ProcessFinancialData(financialData, taxCalculator); // 处理财务数据计算税务
taxCalculator.PrintTaxReport(); // 打印税务报告
Console.WriteLine("\n=== 数据导出 ===");
var dataExporter = new DataExporterVisitor();
ProcessFinancialData(financialData, dataExporter); // 处理财务数据导出
}
// 统一的处理方法
static void ProcessFinancialData(List<IFinancialElement> data, IFinancialVisitor visitor)
{
foreach (var element in data)
{
element.Accept(visitor); // 每个元素接受访问者访问
}
}
}
/* 输出结果:
=== 财务报表生成 ===
收入: 产品销售 - ¥50000
收入: 服务收入 - ¥30000
支出: 员工工资 (人事) - ¥20000
支出: 办公室租金 (办公) - ¥8000
支出: 业务招待 (业务) - ¥3000
支出: 个人消费 (个人) - ¥5000
资产: 办公设备 - 价值: ¥15000, 折旧: ¥3000
资产: 公司车辆 - 价值: ¥80000, 折旧: ¥16000
=== 财务报表 ===
总收入: ¥80000
总支出: ¥36000
总资产: ¥95000
净利润: ¥44000
净资产: ¥139000
=== 税务计算 ===
应税收入: 产品销售 - ¥50000
应税收入: 服务收入 - ¥30000
可抵扣支出: 办公室租金 - ¥8000
可抵扣支出: 业务招待 - ¥3000
资产折旧抵扣: 办公设备 - ¥3000
资产折旧抵扣: 公司车辆 - ¥16000
=== 税务报告 ===
应纳税收入: ¥80000
可抵扣支出: ¥11000
资产折旧: ¥19000
净应纳税收入: ¥50000
应缴税款: ¥12500
=== 数据导出 ===
{"type": "income", "description": "产品销售", "amount": 50000}
{"type": "income", "description": "服务收入", "amount": 30000}
{"type": "expense", "description": "员工工资", "category": "人事", "amount": 20000}
{"type": "expense", "description": "办公室租金", "category": "办公", "amount": 8000}
{"type": "expense", "description": "业务招待", "category": "业务", "amount": 3000}
{"type": "expense", "description": "个人消费", "category": "个人", "amount": 5000}
{"type": "asset", "name": "办公设备", "value": 15000, "depreciation": 3000}
{"type": "asset", "name": "公司车辆", "value": 80000, "depreciation": 16000}
*/
访问者模式的核心价值
1. 分离关注点
- 数据类(IncomeItem, ExpenseItem, AssetItem):只负责存储数据
- 算法类(各种Visitor):只负责处理逻辑
2. 开闭原则
- 对扩展开放:要加新功能(如数据导出),只需新增Visitor,不用修改数据类
- 对修改关闭:数据类一旦稳定,就不需要再改动
3. 实际应用场景
csharp
// 编译器中的访问者模式应用
public interface IASTNode
{
void Accept(IASTVisitor visitor);
}
// 语法树节点
public class VariableDeclarationNode : IASTNode
{
public string Type { get; set; }
public string Name { get; set; }
public void Accept(IASTVisitor visitor) => visitor.VisitVariableDeclaration(this);
}
public class MethodCallNode : IASTNode
{
public string MethodName { get; set; }
public List<IASTNode> Arguments { get; set; }
public void Accept(IASTVisitor visitor) => visitor.VisitMethodCall(this);
}
// 不同的编译器阶段
public interface IASTVisitor
{
void VisitVariableDeclaration(VariableDeclarationNode node);
void VisitMethodCall(MethodCallNode node);
}
public class TypeCheckerVisitor : IASTVisitor { /* 类型检查 */ }
public class CodeGeneratorVisitor : IASTVisitor { /* 代码生成 */ }
public class OptimizerVisitor : IASTVisitor { /* 代码优化 */ }
4. 什么时候使用访问者模式?
✅ 适用场景:
- 对象结构稳定,但需要频繁添加新操作
- 需要对对象结构中的元素进行多种不相关的操作
- 希望避免"污染"对象类的接口
❌ 不适用场景:
- 对象结构经常变化
- 元素类很少增加新操作
- 元素类接口需要经常修改
总结
访问者模式的真正威力在于:让你能在不修改现有类的情况下,为它们添加新的操作。
就像你有一个工具箱(数据类),访问者模式让你可以:
- 用锤子Visitor(报表生成)来敲打
- 用螺丝刀Visitor(税务计算)来拧螺丝
- 用尺子Visitor(数据导出)来测量
所有工具都作用于同一个工具箱,但互不干扰,而且可以随时添加新工具!