设计模式 23
- 创建型模式(5):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
- 结构型模式(7):适配器模式、桥接模式、组合模式、装饰者模式、外观模式、享元模式、代理模式
- 行为型模式(11):责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式
访问者模式(Visitor Pattern)
1 定义
访问者模式通过引入一个访问者接口,使得你可以在元素类中接受访问者,并让访问者决定对元素的具体操作。访问者模式的关键在于分离算法和数据结构,使得新的操作可以轻松地添加而不影响已有的数据结构。
2 结构
访问者模式包含以下角色:
- 访问者接口(Visitor): 为每种元素类型定义一个访问方法。访问者接口通常提供一个
Visit
方法,针对不同的元素类有不同的实现。 - 具体访问者(ConcreteVisitor): 实现访问者接口的具体操作,对元素执行具体的操作。
- 元素接口(Element): 声明一个
Accept
方法,该方法接受访问者对象并调用访问者的Visit
方法。 - 具体元素(ConcreteElement): 实现元素接口,定义元素的具体行为,并在
Accept
方法中调用访问者的对应方法。 - 对象结构(ObjectStructure): 通常是一个包含多个不同类型元素的集合,它可以遍历这些元素并让访问者访问它们。
UML 类图
+---------------------------+ +---------------------------+
| Visitor | <------ | Element |
+---------------------------+ +---------------------------+
| + VisitElementA():void | | + Accept(v:Visitor): void |
| + VisitElementB():void | +---------------------------+
+---------------------------+ ^
^ |
| |
+---------------------------+ +---------------------------+
| ConcreteVisitor | | ConcreteElement |
+---------------------------+ +---------------------------+
| + VisitElementA():void | | + Accept(v:Visitor): void |
| + VisitElementB():void | | + OperationA(): void |
+---------------------------+ +---------------------------+
3 示例代码
假设我们要实现一个报表系统,系统中包含不同类型的员工(如工程师和经理),每种员工有不同的报表要求。我们可以使用访问者模式来实现报表的生成,使得报表的生成与员工类型的实现分离。
访问者接口
// 访问者接口
public interface IVisitor
{
void Visit(Engineer engineer);
void Visit(Manager manager);
}
具体访问者
// 具体访问者 - 报表生成器
public class ReportGenerator : IVisitor
{
public void Visit(Engineer engineer)
{
Console.WriteLine($"Generating report for Engineer: {engineer.Name}");
}
public void Visit(Manager manager)
{
Console.WriteLine($"Generating report for Manager: {manager.Name} with {manager.SubordinatesCount} subordinates.");
}
}
元素接口
// 元素接口
public interface IEmployee
{
void Accept(IVisitor visitor);
}
具体元素类
// 具体元素 - 工程师
public class Engineer : IEmployee
{
public string Name { get; private set; }
public Engineer(string name)
{
Name = name;
}
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
// 具体元素 - 经理
public class Manager : IEmployee
{
public string Name { get; private set; }
public int SubordinatesCount { get; private set; }
public Manager(string name, int subordinatesCount)
{
Name = name;
SubordinatesCount = subordinatesCount;
}
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
}
对象结构
// 对象结构 - 员工列表
public class EmployeeStructure
{
private List<IEmployee> _employees = new List<IEmployee>();
public void Attach(IEmployee employee)
{
_employees.Add(employee);
}
public void Detach(IEmployee employee)
{
_employees.Remove(employee);
}
public void Accept(IVisitor visitor)
{
foreach (var employee in _employees)
{
employee.Accept(visitor);
}
}
}
客户端代码
class Program
{
static void Main(string[] args)
{
// 创建员工结构
EmployeeStructure employeeStructure = new EmployeeStructure();
// 添加员工
employeeStructure.Attach(new Engineer("John"));
employeeStructure.Attach(new Manager("Alice", 5));
// 创建报表生成器
ReportGenerator reportGenerator = new ReportGenerator();
// 生成报表
employeeStructure.Accept(reportGenerator);
}
}
运行结果
Generating report for Engineer: John
Generating report for Manager: Alice with 5 subordinates.
在这个例子中,IVisitor
定义了对不同员工类型(Engineer
和 Manager
)的访问方法。ReportGenerator
是具体的访问者,实现了生成报表的逻辑。IEmployee
接口定义了 Accept
方法,Engineer
和 Manager
作为具体的元素,实现了接受访问者的逻辑。EmployeeStructure
作为对象结构,管理了所有的员工,并允许访问者访问这些员工。
4 特点
-
优点:
-
增加新的操作容易: 可以在不修改元素类的情况下,通过添加新的访问者类来增加新的操作。
-
将操作与对象结构分离: 访问者模式将数据结构和操作分离,使得数据结构和操作各自独立,符合单一职责原则。
-
扩展性好: 可以很容易地增加新的访问者来实现新的功能。
-
-
缺点:
-
增加元素类的复杂性: 每个元素类都必须实现接受访问者的方法,这可能会增加类的复杂性。
-
违反开闭原则: 如果需要修改元素的结构或添加新的元素类型,则需要修改所有的访问者类,这与开闭原则相违背。
-
双分派: 访问者模式要求进行双分派,即根据元素类型和访问者类型分别调用相应的方法,这可能会导致系统的复杂性增加。
-
5 适用场景
- 对象结构稳定: 当对象结构相对稳定,但操作经常变化时,使用访问者模式可以有效地管理这些变化。
- 需要对对象结构中的对象进行复杂操作: 访问者模式适用于对对象结构中的元素进行复杂操作,且这些操作可能会频繁变化的场景。
- 对象结构中包含多个不相关的类: 当对象结构中包含多个不相关的类,需要对这些类执行某些操作时,访问者模式可以通过统一的访问者接口来处理这些操作。
6 总结
访问者模式通过将操作分离到独立的访问者对象中,使得在不修改元素类的情况下,可以增加新的操作。它适用于对象结构稳定但操作经常变化的场景。然而,由于需要对每个元素类增加接受访问者的方法,并且可能导致违反开闭原则,因此在使用时需要权衡利弊。在需要对复杂对象结构进行扩展和管理时,访问者模式是一种强大的设计模式。