104、23种设计模式之访问者模式(13/23)
一、定义
访问者模式是一种行为型设计模式,其核心思想是将数据结构与数据操作解耦。通过定义独立的访问者对象,它允许在不修改现有元素类结构的前提下,为元素添加新的操作。该模式基于双重分派机制(对象结构接受访问者 + 元素调用访问者的具体方法),实现动态行为扩展。
二、应用场景
1.复杂数据结构需多种操作
-
场景:编译器抽象语法树(AST)需执行语法检查、语义分析、代码生成等操作。
-
解决方案:为每个操作定义独立访问者(如TypeCheckerVisitor、CodeGeneratorVisitor),避免修改节点类。
2.对象结构稳定但操作频繁变化
- 场景:电商系统中商品类型固定(手机、电脑),但需支持打折、加入购物车、生成小票等操作。
- 解决方案:通过ShoppingCartVisitor实现不同计算逻辑,元素类仅需实现accept()方法。
3.集中管理相似行为
- 场景:文档处理需支持导出PDF、生成目录等操作。
- 解决方案:访问者模式将导出逻辑聚合在访问者中,避免元素类臃肿。
4.跨层次结构访问
- 场景:图形编辑器中圆形、矩形需支持渲染、导出、计算面积等操作。
- 解决方案:访问者模式统一处理异构对象,避免元素类污染。
5.动态权限控制
- 场景:用户和角色结构稳定,但权限校验逻辑(如RBAC模型)需动态扩展。
- 解决方案:通过访问者实现非侵入式权限计算。
三、优缺点
1.优点
- 扩展性强:新增操作仅需添加访问者类,符合开闭原则。
- 职责分离:数据结构与操作逻辑解耦,提升可维护性。
- 集中化操作:相似行为聚合在访问者中(如报表生成)。
2.缺点
- 元素类扩展困难:新增元素需修改所有访问者接口。
- 破坏封装性:访问者需了解元素内部状态,可能暴露敏感数据。
- 对象结构变化导致访问者修改:元素关系变化会引发级联修改。
四、C# 示例代码
using System;
using System.Collections.Generic;// 抽象元素接口
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 Fruit(string name, double weight) {Name = name;Weight = weight;}public void Accept(IVisitor visitor) {visitor.Visit(this);}
}// 抽象访问者接口
public interface IVisitor {void Visit(Book book);void Visit(Fruit fruit);
}// 具体访问者类:打折计算
public class DiscountVisitor : IVisitor {public void Visit(Book book) {Console.WriteLine($"书籍《{book.Title}》打折后价格:{book.Price * 0.9:C}");}public void Visit(Fruit fruit) {Console.WriteLine($"{fruit.Name}({fruit.Weight}kg)不打折");}
}// 对象结构:管理元素集合
public class ObjectStructure {private List<IElement> elements = new List<IElement>();public void AddElement(IElement element) {elements.Add(element);}public void Accept(IVisitor visitor) {foreach (var element in elements) {element.Accept(visitor);}}
}// 客户端代码
class Program {static void Main(string[] args) {ObjectStructure structure = new ObjectStructure();structure.AddElement(new Book("C# Design Patterns", 59.99));structure.AddElement(new Fruit("Apple", 1.5));IVisitor discountVisitor = new DiscountVisitor();structure.Accept(discountVisitor);}
}
五、关键点总结
1.双重分派机制:
- 元素结构调用Accept()方法,将自身传递给访问者。
- 元素调用访问者的Visit()方法,实现动态绑定。
2.适用条件:
- 对象结构稳定(元素类型不频繁变化)。
- 需要频繁新增操作(如编译器AST处理)。
3.设计原则:
- 开闭原则:对扩展开放,对修改关闭。
- 单一职责原则:访问者集中管理相关行为。
4.替代方案:
- 若元素类型频繁变化,可考虑策略模式或组合模式。
- 若操作简单,直接内联方法可能更高效。
六、典型应用案例
- 编译器设计:遍历AST执行语义分析、代码生成。
- 电商系统:商品打折、生成小票等操作解耦。
- 图形编辑器:统一处理不同图形的渲染和导出。
- 金融计算:风险评估、收益计算等算法扩展。