[转载]访问者模式的简洁实现

[转载]访问者模式的简洁实现 – 蜡笔小巢 – 博客园.

作为《设计模式:可复用面向对象软件的基础》中最复杂的一个模式,访问者模式被限制在一个相对特定的类型体系环境下,经典访问者模式实现上非常有技巧性,同时也带来客户类型与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);
}

其实只需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; });

(注:这里判断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()逻辑很简单,使用匿名委托的情况,甚至可以称之为“半”单因素依赖。

实际项目中,访问者模式的思路可以保留,至于经典模式中“八股”&“繁琐”的实现方式建议还是尽可能避免。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏