动机(Motivation)
在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。
如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?
意图(Intent)
表示一个作用于某对象结构中的各元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。
结构(Structure)
Visitor访问者类:为该对象结构中的ConcreteElement的每一个类声明一个visit操作
ConcreteVisitor具体访问者类:实现每个由visitor声明的操作,每个操作实现算法的一部分,而该算法片断仍是对应于结构中对象的类
ObjectStructure对象结构类:能枚举它的元素,可以提供一个高层的接口允许访问者访问它的元素
Element类:定义一个accept操作,它以一个访问者为参数
ConcereteElement类:具体元素类,实现Accept操作
Visitor模式的几个要点
1.visitor模式通过所谓双重分发(double dispatch)来实现在不更改Element类层次结构的前提下,在运行时透明地为类层次结构上的各个动态添加新的操作。
2.所谓双重分发即visitor模式中间包括了两个多态分支,注意其中的多态机制,第一个为accept方法的多态辨析;第二个为visit方法的多态辨析
3.visitor模式的最大缺点在于扩展类层次结构(添加新的Element类),会导致visitor类的改变。因此Visitor模式适用于“Element类层次结构稳定”,而其中的操作却经常面临频繁变动。
代码实现
人类抽象类,对应于结构图中的Element类,里面定义一个accept方法(操作),该方法以visitor访问者作为一个参数
abstract class Person
{
//接受方法,用来获得状态对象的
public abstract void Accept(Action visitor);
}
Man男人类和Woman女人类是具体元素类,对应于结构图中的ConcreteElement类,实现Element类即人类所定义的Accept操作
//男人类
class Man : Person
{
public override void Accept(Action visitor)
{
visitor.GetManConclusion(this);
}
}
//女人类
class Woman : Person
{
public override void Accept(Action visitor)
{
visitor.GetWomanConclusion(this);
}
}
Action状态抽象类,对应于结构图中的Visitor访问者类,为该结构中的ConcreteElement的每一个类声明一个visit操作,在此实例当中,为男人和女人类分别声明一个GetManConclusion操作和GetWomanConclusion操作
abstract class Action
{
//得到男人结论或反应
public abstract void GetManConclusion(Man concreteElementA);
//得到女人结论或反应
public abstract void GetWomanConclusion(Woman concreteElemanetB);
}
具体状态类:成功类,失败类,恋爱类,对应于结构图当中的ConcreteVisitor,实现Visitor声明的两个操作
class Success : Action
{
public override void GetManConclusion(Man concreteElementA)
{
Console.WriteLine("{0}{1}时,背后多半有一个伟大的女人。",concreteElementA.GetType().Name,this.GetType().Name);
}
public override void GetWomanConclusion(Woman concreteElementB)
{
Console.WriteLine("{0}{1}时,背后大多有一个不成功的男人。",concreteElementB.GetType().Name,this.GetType().Name);
}
}
对象结构类:对应于结构图中的ObjectStructure
class ObjectStructure
{
private IList<Person> elements = new List<Person>();
//增加
public void Attach(Person element)
{
elements.Add(element);
}
//移除
public void Detach(Person element)
{
elements.Remove(element);
}
//查看显示
public void Display(Action visitor)
{
foreach (Person e in elements)
{
e.Accept(visitor);
}
}
}
客户端代码
在对象结构中要加入对比的男人和女人
ObjectStructure o = new ObjectStructure();
o.Attach(new Man());
o.Attach(new Woman());
成功时的反应,Display()方法用于查看在各种状态下,男人和女人的反应
Success v1 = new Success();
o.Display(v1);
失败时的反应
Failing v2 = new Failing();
o.Display(v2);
总结:访问者模式适用于数据结构相对稳定的系统,而又有易于变化的算法,这是适合使用访问者模式的。