访问者模式的简洁实现
转自:《模式——基于工程化实现及扩展》(C#版本,设计模式分册)
作为《设计模式:可复用面向对象软件的基础》中最复杂的一个模式,访问者模式被限制在一个相对特定的类型体系环境下,经典访问者模式实现上非常有技巧性,同时也带来客户类型与Visitor之间交织在一起的双因素依赖关系。对于非常重视结构清晰、实现简洁的大型企业应用而言,经典访问者模式的实现方式“过犹不及”了。
以这段实现为例:
C#
/// visitor 需要影响的Element,Visitable
public interface IEmployee
{
/// 相关属性
string Name { get; set;}
double Income { get; set;}
int VacationDays { get; set;}
void Accept(IVisitor visitor);
}
/// 抽象Visitor接口
public interface IVisitor
{
void VisitEmployee(IEmployee employee);
void VisitManager(Manager manager);
}
/// 一个具体的Element
public class Employee : IEmployee
{
private string name;
private double income;
private int vacationDays;
public Employee(string name, double income, int vacationDays)
{
this.name = name;
this.income = income;
this.vacationDays = vacationDays;
}
public string Name
{
get{return name;}
set{name = value;}
}
public double Income
{
get{return income;}
set{income = value;}
}
public int VacationDays
{
get{return vacationDays;}
set{vacationDays = value;}
}
/// 引入Visitor对自身的操作
public virtual void Accept(IVisitor visitor)
{
visitor.VisitEmployee(this);
}
}
/// 另一个具体的Element
public class Manager : Employee
{
private string department;
public string Department { get { return department; } }
public Manager(string name, double income, int vacationDays,
string department)
: base(name, income, vacationDays)
{
this.department = department;
}
/// 引入Visitor对自身的操作
public override void Accept(IVisitor visitor)
{
visitor.VisitManager(this);
}
}
/// 为了便于对HR系统的对象进行批量处理增加的集合类型
public class EmployeeCollection : List<IEmployee>
{
/// 组合起来的批量Accept操作
public virtual void Accept(IVisitor visitor)
{
foreach (IEmployee employee in this)
employee.Accept(visitor);
}
}
Unit Test
private EmployeeCollection employees = new EmployeeCollection();
/// 具体的Visitor, 增加休假天数
class ExtraVacationVisitor : IVisitor
{
public void VisitEmployee(IEmployee employee)
{
employee.VacationDays += 1;
}
public void VisitManager(Manager manager)
{
manager.VacationDays += 2;
}
}
/// 具体的Visitor, 加薪
class RaiseSalaryVisitor : IVisitor
{
public void VisitEmployee(IEmployee employee)
{
employee.Income *= 1.1;
}
public void VisitManager(Manager manager)
{
manager.Income *= 1.2;
}
}
[TestMethod]
public void Test()
{
employees.Add(new Employee("joe", 25000, 14));
employees.Add(new Manager("alice", 22000, 14, "sales"));
employees.Add(new Employee("peter", 15000, 7));
employees.Accept(new ExtraVacationVisitor());
employees.Accept(new RaiseSalaryVisitor());
IEmployee joe = employees[0];
Assert.AreEqual<double>(25000 * 1.1, joe.Income);
IEmployee peter = employees[2];
Assert.AreEqual<int>(7 + 1, peter.VacationDays);
IEmployee alice = employees[1];
Assert.AreEqual<int>(14 + 2, alice.VacationDays);
Assert.AreEqual<double>(22000 * 1.2, alice.Income);
}
/// visitor 需要影响的Element,Visitable
public interface IEmployee
{
/// 相关属性
string Name { get; set;}
double Income { get; set;}
int VacationDays { get; set;}
void Accept(IVisitor visitor);
}
/// 抽象Visitor接口
public interface IVisitor
{
void VisitEmployee(IEmployee employee);
void VisitManager(Manager manager);
}
/// 一个具体的Element
public class Employee : IEmployee
{
private string name;
private double income;
private int vacationDays;
public Employee(string name, double income, int vacationDays)
{
this.name = name;
this.income = income;
this.vacationDays = vacationDays;
}
public string Name
{
get{return name;}
set{name = value;}
}
public double Income
{
get{return income;}
set{income = value;}
}
public int VacationDays
{
get{return vacationDays;}
set{vacationDays = value;}
}
/// 引入Visitor对自身的操作
public virtual void Accept(IVisitor visitor)
{
visitor.VisitEmployee(this);
}
}
/// 另一个具体的Element
public class Manager : Employee
{
private string department;
public string Department { get { return department; } }
public Manager(string name, double income, int vacationDays,
string department)
: base(name, income, vacationDays)
{
this.department = department;
}
/// 引入Visitor对自身的操作
public override void Accept(IVisitor visitor)
{
visitor.VisitManager(this);
}
}
/// 为了便于对HR系统的对象进行批量处理增加的集合类型
public class EmployeeCollection : List<IEmployee>
{
/// 组合起来的批量Accept操作
public virtual void Accept(IVisitor visitor)
{
foreach (IEmployee employee in this)
employee.Accept(visitor);
}
}
Unit Test
private EmployeeCollection employees = new EmployeeCollection();
/// 具体的Visitor, 增加休假天数
class ExtraVacationVisitor : IVisitor
{
public void VisitEmployee(IEmployee employee)
{
employee.VacationDays += 1;
}
public void VisitManager(Manager manager)
{
manager.VacationDays += 2;
}
}
/// 具体的Visitor, 加薪
class RaiseSalaryVisitor : IVisitor
{
public void VisitEmployee(IEmployee employee)
{
employee.Income *= 1.1;
}
public void VisitManager(Manager manager)
{
manager.Income *= 1.2;
}
}
[TestMethod]
public void Test()
{
employees.Add(new Employee("joe", 25000, 14));
employees.Add(new Manager("alice", 22000, 14, "sales"));
employees.Add(new Employee("peter", 15000, 7));
employees.Accept(new ExtraVacationVisitor());
employees.Accept(new RaiseSalaryVisitor());
IEmployee joe = employees[0];
Assert.AreEqual<double>(25000 * 1.1, joe.Income);
IEmployee peter = employees[2];
Assert.AreEqual<int>(7 + 1, peter.VacationDays);
IEmployee alice = employees[1];
Assert.AreEqual<int>(14 + 2, alice.VacationDays);
Assert.AreEqual<double>(22000 * 1.2, alice.Income);
}
其实只需2行即可完成

employees.Where(x => x.GetType() == typeof(Employee)).ToList().ForEach(x=>{ x.Income *= 1.1; x.VacationDays++;});
employees.Where(x => x.GetType() == typeof(Manager)).ToList().ForEach(x => { x.Income *= 1.2; x.VacationDays+=2; });
employees.Where(x => x.GetType() == typeof(Manager)).ToList().ForEach(x => { x.Income *= 1.2; x.VacationDays+=2; });
(注:这里判断x => x.GetType() == typeof(Employee) 没有用is语句,因为Manager: Employee,因此如果不做精确匹配,Manager类型的实例会连续执行两次处理)
示例非常短小,但同样能达到前面IVisitor、IElement绕来绕去双因素依赖后的效果。为什么?
- 首先,Visitor是通过ForEach()语句赋予的,相当于Accept(Visitor)过程,实际的Visitor就是个System.Action<EmployeeBase>委托。
- 其次,Visitor的Visit()处理则是在ForEach()过程中执行的,employees中所有满足条件的类型会随着ForEach的迭代依次执行System.Action<EmployeeBase>委托指向的处理,这里采取了最简化的处理,委托的指向为一个LAMADA语法定义的匿名方法,例如:对于Employee类型就是x=>{ x.Income *= 1.1; x.VacationDays++;}。
- 实际项目中,如果处理过程特别复杂,您完全可以让执行System.Action<EmployeeBase>委托指向独立的方法、类甚至外部的某个程序集中的处理逻辑。
- 从效果看,IElement类型与IVisitor间的“脐带”也用委托剪断了,依赖关系大为简化,从双因素依赖变为单因素依赖,对于Visit()逻辑很简单,使用匿名委托的情况,甚至可以称之为“半”单因素依赖。
实际项目中,访问者模式的思路可以保留,至于经典模式中“八股”&“繁琐”的实现方式建议还是尽可能避免。
贸易电子化,技术全球化
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述