访问者模式

# 分类

行为型模式

# 定义

  • 访问者模式(Visitor Pattern) 顾名思义使用了这个模式后就可以在不修改已有程序结构的前提下,通过添加额外的访问者来完成对已有代码功能的扩展。
  • 设计模式 一书中对于访问者模式给出的定义为表示一个作用于某结构对象中各元素的操作,它使你可以在不改变各元素类的提前下定义作用于这些元素的新操作。

# 意图

  • 访问者模式的目的是把数据结构数据操作分离。

# 应用场景

  • 访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。
  • 如果新增的需求是为数不多的几次变动,而且你不用为了一个需求的调整而将整个类层次结构统统地修改一遍,那么直接在原有类层次结构上修改也许是个不错的主意。如果需求不停的发生变更,并且整个变更会导致整个类层次结构被修改个底朝天,这时就需要重构并尝试下访问者模式了。
  • 从定义可以看出结构对象是使用访问者模式的必须条件,而且这个结构对象必须存在遍历自身各个对象的方法。
  • 如果系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式是比较合适的,因为访问者模式使得算法操作的增加变得容易。反之,如果系统的数据结构对象易于变化,经常要有新的数据对象增加进来,就不适合使用访问者模式。
  • 业务规则要求遍历多个不同对象,这本身也是访问者模式的出发点,迭代器模式只能访问同类或同接口的数据,访问者模式是对迭代器模式的扩充,可以遍历不同的对象,然后执行不同的操作,也就是针对访问的对象不同,执行不同的操作。

应用实例

  • 您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断。
  • 男人女人对比的例子。

# 角色与结构图

角色包括:ObjectStructure(对象结构,批量执行不同的操作)Element(元素,种类一般比较稳定)Visitor(访问者,一般可以动态扩展)

  • Visitor:抽象访问者角色 抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。
  • ConcreteVisitor:具体访问者角色 实现由抽象访问者角色声明的每个操作。访问者访问到一个类后该怎么干,具体要做什么事情。
  • Element:抽象元素角色 接口或者抽象类,定义一个Accept操作,它以一个访问者为参数。
  • ConcreteElement:具体元素角色 实现Accept方法,通常是visitor.visit(this),基本上都形成了一个套路了。
  • ObjectStruture:结构对象角色 容纳多个不同类、不同接口的容器。

下图解释了访问者模式中各角色的作用

<<------------------------访问者模式结构图--------------------------->>

# 示例代码

//访问者抽象类,按照数据元素定义固定个数的行为
abstract class Visitor
{
  public abstract void Visit(ConcreteElementA concreteElementA);
  public abstract void Visit(ConcreteElementB concreteElementB);
}

//访问者1,实现所有接口
class ConscreteVisitor1 : Visitor
{
  public override void Visit(ConcreteElementA concreteElementA){
    Console.WriteLine("{0}被{1}访问",concreteElementA.GetType(),this.GetType().Name);
  }

  public override void Visit(ConcreteElementB concreteElementB){
    Console.WriteLine("{0}被{1}访问",concreteElementB.GetType(),this.GetType().Name);
  }
}

//访问者2,实现所有接口
class ConscreteVisitor2 : Visitor
{
  public override void Visit(ConcreteElementA concreteElementA){
    Console.WriteLine("{0}被{1}访问",concreteElementA.GetType(),this.GetType().Name);
  }

  public override void Visit(ConcreteElementB concreteElementB){
    Console.WriteLine("{0}被{1}访问",concreteElementB.GetType(),this.GetType().Name);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//定义数据结构抽象类
abstract class Element {
  public abstract void Accept(Visitor visitor);
}

class ConcreteElementA : Element{
  public override void Accept(Visitor visitor){
    visitor.Visit(this);
  }

  public void OperationA(){
     其他操作A......
  }
}

class ConcreteElementB : Element{
  public override void Accept(Visitor visitor){
    visitor.Visit(this);
  }

   public void OperationB(){
     其他操作B......
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素
class ObjectStructure{
  private IList<Element> elements = new List<Element>();
  
  public void Attach(Element element){
    elements.Add(element);
  }

  public void Dettach(Element element){
    elements.Remove(element);
  }

  public void Accept(Visitor visitor){
    foreach(Element element in elements){
      e.Accept(visitor);
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//客户端
static void main(String[] args) {
  ObjectStructure objects = new ObjectStructure();

  objects.Attach(new ConcreteElementA());
  objects.Attach(new ConcreteElementB());

  ConcreteVisitor1 v1 = new ConcreteVisitor1();
  ConcreteVisitor2 v2 = new ConcreteVisitor2();

  objects.Accept(v1);
  objects.Accept(v2);

  Console.ReadLine();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//扩展一个访问者,其他都很稳定不用改变
class ConscreteVisitor3 : Visitor
{
  public override void Visit(ConcreteElementA concreteElementA){
    Console.WriteLine("{0}被{1}访问",concreteElementA.GetType(), this.GetType().Name);
  }

  public override void Visit(ConcreteElementB concreteElementB){
    Console.WriteLine("{0}被{1}访问",concreteElementB.GetType(), this.GetType().Name);
  }
}
1
2
3
4
5
6
7
8
9
10
11
//客户端也仅仅是增加下面的代码
ConcreteVisitor3 v3 = new ConcreteVisitor3();
objects.Accept(v3);
1
2
3

# 优点

  • 符合单一职责原则
  • 易于扩展,符合开-闭原则,增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者。
  • 访问者模式将与元素相关的行为都集中到一个访问者对象中。
  • 具体元素类负责数据加载,具体访问者负责行为实施,两个不同的职责非常明确的分离开来,各自演绎而变化,其次由于职责分开,继续增加对数据的操作会非常快捷。
  • 把数据扔给访问者,由访问者来执行内部功能。

# 缺点

  • 违反迪米特法则,访问者要访问一个类就必然要求这个类公布一些方法和属性,也就是说访问者关注了其他类的内部细节,这样会破坏类的封装性。
  • 违反了依赖倒置原则访问者Visitor的行为(操作)依赖于具体元素对象ConcreateElement
  • 在访问者模式中,元素与访问者之间能够传递的信息有限,这往往也会限制访问者模式的使用。
  • 具体元素的变更(增、减)较为困难,元素的变更会导致访问者发生剧烈的变化。

# 小结

  • 主要解决稳定的数据结构易变的操作耦合问题。
  • 该模式具有稳定的程序结构,一切扩展都通过新建的访问者来实现。
  • 这是一个巧妙而且复杂的模式,它的使用条件比较苛刻。当系统中存在着固定的数据结构,而有着不同的行为,那么访问者模式也许是个不错的选择。
  • 双重分派:第一次分派是在客户程序中将具体访问者模式作为参数传递给具体元素角色,第二次分派是进入具体元素角色后,调用以具体元素角色自身为参数的具体访问者模式中的visitor方法。
  • 何时使用?需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作污染这些对象的类,使用访问者模式将这些操作封装到一个类中。
  • 如何解决?在被访问的类里面加一个对外提供接待访问者的接口。
  • 关键代码?在数据基础类里面有一个方法接受访问者,将自身引用传入访问者的方法。
  • 访问者模式可以对功能进行统一,可以做报表、UI\拦截器与过滤器。

# 一句话概括

不改变数据结构的前提下,增加作用于一组对象元素的新功能。

上次更新: 2025/03/22, 13:47:44
最近更新
01
Git问题集合
01-29
02
安装 Nginx 服务器
01-25
03
安装 Docker 容器
01-25
更多文章>
×
×