访问者模式的简洁实现

转自:《模式——基于工程化实现及扩展》(C#版本,设计模式分册)

 

作为《设计模式:可复用面向对象软件的基础》中最复杂的一个模式,访问者模式被限制在一个相对特定的类型体系环境下,经典访问者模式实现上非常有技巧性,同时也带来客户类型与Visitor之间交织在一起的双因素依赖关系。对于非常重视结构清晰、实现简洁的大型企业应用而言,经典访问者模式的实现方式“过犹不及”了。

 

以这段实现为例:

 

C#  
/// visitor 需要影响的Element,Visitable
public interface IEmployee
{
    
/// 相关属性
    string Name { getset;}
    
double Income { getset;}
    
int VacationDays { getset;}

    
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"2500014));
    employees.Add(
new Manager("alice"2200014"sales"));
    employees.Add(
new Employee("peter"150007));

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

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

posted @ 2010-08-10 17:13  蜡笔小王  阅读(2332)  评论(14编辑  收藏  举报