28. 访问者模式
约 1522 字大约 5 分钟
2026-03-29
定义
访问者模式(Visitor),表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
人话:访问者模式就像“男人”和“女人”面对不同情境时的表现分析。在这个场景里,“男人”和“女人”是固定不变的对象结构,但我们可以用不同的“观察角度”去分析他们,比如成功时的表现、失败时的表现、恋爱时的表现等等。
如果不用访问者模式来设计,就相当于把所有情况都写在“男人类”和“女人类”里面,比如:
- 成功时做什么
- 失败时做什么
- 恋爱时做什么
这样一来,每增加一种新的“状态”(比如新增“结婚状态”),就必须修改所有相关类(男人、女人),代码会越来越臃肿。这在软件中就属于数据结构稳定,但行为不断变化,导致类频繁修改、扩展困难。
而使用访问者模式之后,我们会这样设计:
- “男人类”和“女人类”保持不变(Element)
- 每一种“行为/状态”单独做成一个类(Visitor)
- 成功访问者
- 失败访问者
- 恋爱访问者
然后让这些“访问者”去作用于“男人”和“女人”。
这样一来:
- 男人、女人 → 是稳定的数据结构(不会轻易改)
- 成功、失败、恋爱 → 是不断扩展的行为(可以随便加)
- 新增行为时 → 只需要新增一个访问者类,不需要修改原有类
比如:
- 成功访问者访问男人 → 输出“男人成功时很有成就感”
- 成功访问者访问女人 → 输出“女人成功时很自豪”
- 失败访问者访问男人 → 输出“男人失败时闷头喝酒”
- 失败访问者访问女人 → 输出“女人失败时容易流泪”
在软件中也是一样:访问者模式把“数据结构”和“作用于它的操作”分离开来。当数据结构比较稳定,但操作经常变化时,就可以使用访问者模式,从而避免频繁修改原有类,让系统更容易扩展。
解释器模式(interpreter)结构图
Visitor类——抽象访问者:
// Visitor(抽象访问者)
abstract class Visitor
{
public abstract void VisitConcreteElementA(ConcreteElementA elementA);
public abstract void VisitConcreteElementB(ConcreteElementB elementB);
}ConcreteVisitor类——具体访问者:
// ConcreteVisitor1
class ConcreteVisitor1 : Visitor
{
public override void VisitConcreteElementA(ConcreteElementA elementA)
{
Console.WriteLine($"{elementA.GetType().Name} 被 {this.GetType().Name} 访问");
}
public override void VisitConcreteElementB(ConcreteElementB elementB)
{
Console.WriteLine($"{elementB.GetType().Name} 被 {this.GetType().Name} 访问");
}
}
// ConcreteVisitor2
class ConcreteVisitor2 : Visitor
{
public override void VisitConcreteElementA(ConcreteElementA elementA)
{
Console.WriteLine($"{elementA.GetType().Name} 被 {this.GetType().Name} 访问");
}
public override void VisitConcreteElementB(ConcreteElementB elementB)
{
Console.WriteLine($"{elementB.GetType().Name} 被 {this.GetType().Name} 访问");
}
}Element类——抽象元素:
// Element(抽象元素)
abstract class Element
{
public abstract void Accept(Visitor visitor);
}ConcreteElement类——具体元素:
// ConcreteElementA
class ConcreteElementA : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementA(this);
}
public void OperationA()
{
// 其他相关方法
}
}
// ConcreteElementB
class ConcreteElementB : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementB(this);
}
public void OperationB()
{
// 其他相关方法
}
}ObjectStructure类——对象结构:
// ObjectStructure(对象结构)
class ObjectStructure
{
private IList<Element> elements = new List<Element>();
public void Attach(Element element)
{
elements.Add(element);
}
public void Detach(Element element)
{
elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach (Element e in elements)
{
e.Accept(visitor);
}
}
}客户端代码:
static void Main(string[] args)
{
ObjectStructure o = new ObjectStructure();
o.Attach(new ConcreteElementA());
o.Attach(new ConcreteElementB());
Visitor v1 = new ConcreteVisitor1();
Visitor v2 = new ConcreteVisitor2();
o.Accept(v1);
o.Accept(v2);
Console.Read();
}运行结果:
ConcreteElementA 被 ConcreteVisitor1 访问
ConcreteElementB 被 ConcreteVisitor1 访问
ConcreteElementA 被 ConcreteVisitor2 访问
ConcreteElementB 被 ConcreteVisitor2 访问访问者模式适用于数据结构相对稳定的系统。它把数据结构和作用于结构上的操作之间的合解脱开,使得操作集合可以相对自由地 演化。
访问者模式的目的是要把处理从数据结构分离出来。很多系统可以按照算法和数据结构分开,如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的增加变得容易。反之,如果这样的系统的数据结构对象易于变化,经常要有新的数据对象增加进来,就不适合使用访问者模式。
访问者模式的优点就是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。访问者模式将有关的行为集中到一个访问者对象中。
访问者的缺点是使增加新的数据结构变得困难了。
所以GoF四人中的一个作者就说过:‘大多时候你并不需要访问者模式,但当一旦你需要访问者模式时,那就是真的需要它了。’事实上,我们很难找到数据结构不变化的情况,所以用访问者模式的机会也就不太多了。这也就是为什么你谈到男人女人对比时我很高兴和你讨论的原因,因为人类性别这样的数据结构是不会变化的。
