访问者模式的简洁实现
转自:《模式——基于工程化实现及扩展》(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()逻辑很简单,使用匿名委托的情况,甚至可以称之为“半”单因素依赖。
实际项目中,访问者模式的思路可以保留,至于经典模式中“八股”&“繁琐”的实现方式建议还是尽可能避免。
贸易电子化,技术全球化