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

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

访问者模式的真正价值

当前代码的问题分析

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

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(数据导出)来测量

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

相关推荐
疯狂的Alex8 小时前
【C#避坑实战系列文章15】C# WinForm 上位机开发:解决串口粘包+LiveCharts卡顿+InfluxDB存储(免费代码+仿真工具)
sqlite·c#·上位机·串口通信·livechars·c#硬件对接
ajassi200021 小时前
开源 C# 快速开发(十六)数据库--sqlserver增删改查
windows·开源·c#
大飞pkz1 天前
【设计模式】观察者模式
开发语言·观察者模式·设计模式·c#
唐青枫1 天前
深入掌握 FluentMigrator:C#.NET 数据库迁移框架详解
c#·.net
李宥小哥1 天前
C#基础08-面向对象
开发语言·c#
李宥小哥1 天前
C#基础07-类与对象
服务器·数据库·c#
包达叔1 天前
仿NewLife的XmlConfig类实现Json配置文件
c#·json·newlife
大飞pkz1 天前
【设计模式】解释器模式
开发语言·设计模式·c#·解释器模式
敲敲敲-敲代码1 天前
web系统(asp.net和C#)
前端·c#·asp.net