【设计模式】访问者模式(Visitor Pattern): visitor.visit(), accept()

访问者模式(Visitor Pattern)简介

定义

访问者模式是一种行为型设计模式,它允许我们向一个类的对象添加新的操作,而不改变该类的定义。访问者模式将操作的定义与对象结构分离,使得操作可以独立地扩展。

适用场景
  1. 对象结构稳定,操作易变:如果需要频繁为一个对象结构添加新的操作,而对象结构本身不常变化。
  2. 跨多个类的操作:需要对不同类的对象执行多种操作,但不希望在这些类中直接添加操作逻辑。
  3. 需要避免对象结构类污染:不想将太多方法添加到对象结构类中。
优缺点

优点:

  1. 扩展性好:可以很方便地增加新的操作,而不影响原有类。
  2. 遵循单一职责原则:将操作逻辑和对象结构分离。
  3. 遵循开闭原则:对于添加新的操作开放,而不修改对象结构。

缺点:

  1. 违反依赖倒置原则:具体元素需要依赖于访问者接口。
  2. 增加复杂性:对于频繁变化的对象结构,修改代价较大。
  3. 双重分派:在某些语言中实现需要使用双重分派机制。

访问者模式的结构

访问者模式包含以下几个核心角色:

  1. Visitor(访问者接口):定义对对象结构中各类元素的访问操作。
  2. ConcreteVisitor(具体访问者):实现访问者接口,提供具体的操作。
  3. Element(元素接口) :定义一个接受访问者的方法(Accept),以便访问者可以访问其数据。
  4. ConcreteElement(具体元素) :实现元素接口,具体实现Accept方法。
  5. ObjectStructure(对象结构):存储不同类型的元素,可以让访问者逐一访问这些元素。

C# 实现访问者模式

以下是一个简单的 C# 示例,演示如何使用访问者模式:

复制代码
using System;
using System.Collections.Generic;

// 访问者接口
public interface IVisitor
{
    void Visit(Book book);
    void Visit(Fruit fruit);
}

// 元素接口
public interface IElement
{
    void Accept(IVisitor visitor);
}

// 具体元素 - 书
public class Book : IElement
{
    public string Title { get; set; }
    public double Price { get; set; }

    public Book(string title, double price)
    {
        Title = title;
        Price = price;
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// 具体元素 - 水果
public class Fruit : IElement
{
    public string Name { get; set; }
    public double Weight { get; set; }
    public double PricePerKg { get; set; }

    public Fruit(string name, double weight, double pricePerKg)
    {
        Name = name;
        Weight = weight;
        PricePerKg = pricePerKg;
    }

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// 具体访问者 - 打印信息
public class PrintVisitor : IVisitor
{
    public void Visit(Book book)
    {
        Console.WriteLine($"Book: {book.Title}, Price: {book.Price}");
    }

    public void Visit(Fruit fruit)
    {
        Console.WriteLine($"Fruit: {fruit.Name}, Weight: {fruit.Weight}kg, Price/kg: {fruit.PricePerKg}");
    }
}

// 具体访问者 - 计算总价格
public class PriceVisitor : IVisitor
{
    public double TotalPrice { get; private set; } = 0;

    public void Visit(Book book)
    {
        TotalPrice += book.Price;
    }

    public void Visit(Fruit fruit)
    {
        TotalPrice += fruit.Weight * fruit.PricePerKg;
    }
}

// 对象结构
public class ShoppingCart
{
    private readonly List<IElement> _items = new List<IElement>();

    public void AddItem(IElement item)
    {
        _items.Add(item);
    }

    public void Accept(IVisitor visitor)
    {
        foreach (var item in _items)
        {
            item.Accept(visitor);
        }
    }
}

// 测试代码
class Program
{
    static void Main(string[] args)
    {
        // 创建购物车并添加商品
        var cart = new ShoppingCart();
        cart.AddItem(new Book("C# Programming", 29.99));
        cart.AddItem(new Fruit("Apple", 2.5, 3.99));
        cart.AddItem(new Fruit("Banana", 1.2, 1.49));

        // 打印商品信息
        var printVisitor = new PrintVisitor();
        cart.Accept(printVisitor);

        // 计算总价
        var priceVisitor = new PriceVisitor();
        cart.Accept(priceVisitor);
        Console.WriteLine($"Total Price: {priceVisitor.TotalPrice}");
    }
}

代码说明

  1. 访问者接口(IVisitor) : 定义了访问Book和Fruit对象的操作。
  2. 元素接口(IElement) : 定义了Accept方法,接收访问者。
  3. 具体元素 : Book和Fruit实现了IElement接口。
  4. 具体访问者 : PrintVisitor实现了打印功能,PriceVisitor计算总价。
  5. 对象结构 : ShoppingCart管理一组IElement,并允许访问者访问它们。
  6. 双重分派 : 通过Accept方法实现访问者和元素的双重分派,访问者根据元素类型执行相应操作。

访问者模式的用途

  • 报告生成器:如针对不同类型的对象生成各种报表。
  • 编译器:在抽象语法树上操作。
  • 数据分析工具:对不同类型的节点执行特定的分析逻辑。

通过访问者模式,您可以更灵活地扩展操作逻辑,同时保持对象结构的稳定性。

相关推荐
淡海水1 小时前
05-02-源码-Boehm GC标记阶段
算法·unity·c#·游戏引擎·gc
信可维1 小时前
AI 读 Word 表格总读错?先查合并单元格和表头这 3 处
开发语言·人工智能·c#·word·软件工程
日和雫2 小时前
封装、继承、多态 & 组合,为什么组合大于继承?
设计模式
半摆烂日常3 小时前
手机表单的左滑操作:触摸事件的处理细节
大数据·开发语言·人工智能·智能手机·https·c#
绛橘色的日落(。・∀・)ノ4 小时前
C#学习笔记
笔记·学习·c#
asdzx674 小时前
C# 将 HTML 转换为 PDF:基于 Chrome 的转换方案
pdf·c#·html
1-1=05 小时前
.NET 实现后台任务
c#·.net·.netcore·quartz·hangfire
步步为营DotNet5 小时前
从分代预算到 DATAS:.NET GC 调优到底在调什么
c#·.net·内存管理·gc·datas
淡海水5 小时前
05-03-源码-Boehm GC分配与碎片管理
开发语言·算法·unity·c#·游戏引擎·gc
代钦塔拉5 小时前
【C++/Qt 进阶】设计模式系列(6):桥接模式——把“两个独立变化的维度“拆开
c++·qt·设计模式