【设计模式】访问者模式(Visitor Pattern): visitor.visit(), accept()
访问者模式(Visitor Pattern)简介
定义
访问者模式是一种行为型设计模式,它允许我们向一个类的对象添加新的操作,而不改变该类的定义。访问者模式将操作的定义与对象结构分离,使得操作可以独立地扩展。
适用场景
- 对象结构稳定,操作易变:如果需要频繁为一个对象结构添加新的操作,而对象结构本身不常变化。
- 跨多个类的操作:需要对不同类的对象执行多种操作,但不希望在这些类中直接添加操作逻辑。
- 需要避免对象结构类污染:不想将太多方法添加到对象结构类中。
优缺点
优点:
- 扩展性好:可以很方便地增加新的操作,而不影响原有类。
- 遵循单一职责原则:将操作逻辑和对象结构分离。
- 遵循开闭原则:对于添加新的操作开放,而不修改对象结构。
缺点:
- 违反依赖倒置原则:具体元素需要依赖于访问者接口。
- 增加复杂性:对于频繁变化的对象结构,修改代价较大。
- 双重分派:在某些语言中实现需要使用双重分派机制。
访问者模式的结构
访问者模式包含以下几个核心角色:
Visitor
(访问者接口):定义对对象结构中各类元素的访问操作。ConcreteVisitor
(具体访问者):实现访问者接口,提供具体的操作。Element
(元素接口):定义一个接受访问者的方法(Accept
),以便访问者可以访问其数据。ConcreteElement
(具体元素):实现元素接口,具体实现Accept
方法。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}");
}
}
代码说明
- 访问者接口(
IVisitor
): 定义了访问Book
和Fruit
对象的操作。 - 元素接口(
IElement
): 定义了Accept
方法,接收访问者。 - 具体元素:
Book
和Fruit
实现了IElement
接口。 - 具体访问者:
PrintVisitor
实现了打印功能,PriceVisitor
计算总价。 - 对象结构:
ShoppingCart
管理一组IElement
,并允许访问者访问它们。 - 双重分派: 通过
Accept
方法实现访问者和元素的双重分派,访问者根据元素类型执行相应操作。
访问者模式的用途
- 报告生成器:如针对不同类型的对象生成各种报表。
- 编译器:在抽象语法树上操作。
- 数据分析工具:对不同类型的节点执行特定的分析逻辑。
通过访问者模式,您可以更灵活地扩展操作逻辑,同时保持对象结构的稳定性。