老生常谈:访问者模式
访问者模式的目的: 封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
示例:假设有一个员工报销的集合,里面存储了员工姓名和报销金额情况,而报销数据类型并不统一,有的是整形的,有的是双精度的,有的是字符串型,我们要做的就是根据具体的数据类型给出不同的显示格式,例如在整型数据后面加一个"C",(是我自己想的,整型的符号一时没想起来),在双数度数据后面加一个"D",浮点数据后面加"F",而字符串则什么也不加。
传统实现方式:遍历集合中的元素,判断元素类型,然后做相应的处理。代码如下:会发现就会出现必须对元素类型做类型判断的条件转移语句,如果判断多了,要想维护起来也并不容易。此时可考虑采用访问者模式。
_arr.Add("一百元");
_arr.Add(100);
_arr.Add(150.5);
foreach (object o in _arr )
{
if (o is string)
{
Console.WriteLine(o.ToString());
}
else if (o is double)
{
Console .WriteLine (o.ToString ()+" D");
}
else if (o is Int32)
{
Console.WriteLine(o.ToString()+" C");
}
}
访问者模式适用性:
2:被访问的类结构非常稳定的情况下使用。系统很少出现需要加入新节点的情况。如果出现需要加入新节点的情况,那么就必须在每一个访问对象里加入一个对应于这个新节点的访问操作,而这是对一个系统的大规模修改,因而是违背"开一闭"原则的。
访问者模式的结构:
1:抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口。
/// 抽象访问者(Visitor)角色
/// </summary>
abstract class Visitor
{
// Methods
abstract public void Visit(Element element);
}
2:具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。
/// 具体访问者(ConcreteVisitor)角色
/// 打印双精度数据
/// 在数据后面加 D
/// </summary>
class DoubleVisitor : Visitor
{
// Methods
public override void Visit(Element element)
{
Employee employee = ((Employee)element);
if (employee._Data.GetType().ToString ()!="System.Double")
{ return; }
Console.WriteLine("{0}'s data: {1}D",
employee.Name, employee._Data );
}
}
/// <summary>
/// 具体访问者(ConcreteVisitor)角色2
/// 打印整型数据
/// 在数据后面加 C
/// </summary>
class Int32Visitor : Visitor
{
public override void Visit(Element element)
{
Employee employee = ((Employee)element);
if (employee._Data.GetType().ToString() != "System.Int32")
{ return; }
Console.WriteLine("{0}'s data : {1}C",
employee.Name, employee._Data );
}
}
3:抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为一个参量。
/// 抽象节点(Node)角色
/// </summary>
abstract class Element
{
// Methods
abstract public void Accept(Visitor visitor);
}
4:具体节点(Node)角色:实现了抽象元素所规定的接受操作。
/// 具体节点(Node)角色
/// </summary>
class Employee : Element
{
// Fields
string name;
private object _data;
public object _Data
{
get { return this._data ; }
set { this._data = value; }
}
// Constructors
public Employee(string _name,object t)
{
this.name = _name;
this._Data = t;
}
// Properties
public string Name
{
get { return name; }
set { name = value; }
}
// Methods
public override void Accept(Visitor visitor)
{
visitor.Visit(this);
}
}
5:结构对象(ObiectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集。
/// 结构对象(ObiectStructure)角色
/// </summary>
class Employees
{
// Fields
private ArrayList employees = new ArrayList();
// Methods
public void Attach(Employee employee)
{
employees.Add(employee);
}
public void Detach(Employee employee)
{
employees.Remove(employee);
}
public void Accept(Visitor visitor)
{
foreach (Employee e in employees)
e.Accept(visitor);
}
}
6:客户端代码:
Employees e = new Employees();
e.Attach(new Employee("用户A:", 25000.0));
e.Attach(new Employee("用户B:", 10000));
e.Attach(new Employee("用户C:", "45000.0RMB"));
// Create two visitors
DoubleVisitor v1 = new DoubleVisitor();
Int32Visitor v2 = new Int32Visitor();
StringVisitor v3 = new StringVisitor();
// Employees are visited
e.Accept(v1);
e.Accept(v2);
e.Accept(v3);
结构对象会遍历它自己所保存的聚集中的所有节点,在本系统中只有一个节点Employee。这个访问是由以下的操作组成的:
1. Employee对象的接受方法Accept被调用;
2. Employee对象反过来调用访问者对象的Visit方法,并将对象本身传入;
3. 访问者对象调用本身的Visit方法。
访问者模式有如下的优点:
1. 访问者模式使得增加新的操作变得很容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,增加新的操作会很复杂。而使用访问者模式,增加新的操作就意味着增加一个新的访问者类,因此,变得很容易。
2. 访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。
3. 访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。迭代子只能访问属于同一个类型等级结构的成员对象,而不能访问属于不同等级结构的对象。访问者模式可以做到这一点。
访问者模式有如下的缺点:
1. 增加新的节点类变得很困难。每增加一个新的节点都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。
2. 破坏封装。访问者模式要求访问者对象访问并调用每一个节点对象的操作,这隐含了一个对所有节点对象的要求:它们必须暴露一些自己的操作和内部状态。不然,访问者的访问就变得没有意义。由于访问者对象自己会积累访问操作所需的状态,从而使这些状态不再存储在节点对象中,这也是破坏封装的。
注:
本文引用:《Java与模式》
http://www.cnblogs.com/zhenyulu/articles/79719.html