当前位置: 首页 > news >正文

c#设计模式—访问者模式

更实际的例子来解释它(访问者模式)的威力。

访问者模式的真正价值

当前代码的问题分析

代码展示了机制,但没有展示为什么需要这种机制。让我用一个财务报表的例子来演示:

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. 实际应用场景

// 编译器中的访问者模式应用
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(数据导出)来测量

所有工具都作用于同一个工具箱,但互不干扰,而且可以随时添加新工具!

http://www.dtcms.com/a/426973.html

相关文章:

  • 【大数据实战】如何从0到1构建用户画像系统(案例+数据仓库+Airflow调度)
  • 打破数据枷锁:在AWS上解锁Oracle数据库的无限潜能
  • 广州网站推广公司wordpress备份恢复阿里云
  • 不用装专业软件!reaConverter:PSD 转 JPG、PDF 转图片
  • 大模型训练流程及GPU内存解析(110)
  • 学习Python中Selenium模块的基本用法(18:使用ActionChains操作鼠标)
  • 从UI到UE:企业级软件如何做出“高端感”的桌面端界面设计
  • 服务专业的建网站公司电话新站优化案例
  • QCustomPlot 核心功能与图表设置(下)——高级功能实现
  • 莱芜网站排名价格珠海高端网站建设
  • 运营商数据安全的垂直破局:技术适配与场景深耕的双重进化
  • 《Local_Pdf_Chat_RAG 深度学习笔记:PDF 本地化对话的 RAG 原理与实践》
  • Node.js 完全安装与使用指南:Windows 平台详细教程
  • jsp在网站开发中的优势番禺制作网站系统
  • 【Rust GUI开发入门】编写一个本地音乐播放器(5. 制作音乐列表组件)
  • 成都哪家公司做网站比较好h5网站建设机构
  • 少儿舞蹈小程序(20):手机号登录与多角色注册
  • 淘宝扭蛋机小程序的社交化运营策略
  • 跨会话泄露:AI时代下的安全挑战与防御策略
  • Nginx if指令安全使用指南
  • AI模型测评平台工程化实战十二讲(第五讲:大模型测评分享功能:安全、高效的结果展示与协作)
  • 2025文档管理软件推荐:效率、安全与协作全解析
  • 包头网站建设价格北京到广州高铁多长时间
  • 网站引导页分为三个板块设计风格天津站建站时间
  • HTML应用指南:利用POST请求获取全国中国工商农业银行网点位置信息
  • 【目标检测2025】
  • FLASK与JAVA的文件互传(单文件互传亲测)
  • Spring Boot + MyBatis plus + MySQL 实现位置直线距离实时计算
  • 大数据Spark(六十四):Spark算子介绍
  • 网页网站设计制作微信推广网站