0%

在现实生活中,有些集合对象中存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同。

这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。

访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。

访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下:

  • 扩展性好:能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好:可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  • 灵活性好:访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  • 符合单一职责原则:访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者(Visitor)模式的主要缺点如下:

  • 增加新的元素类很困难:在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  • 破坏封装:访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  • 违反了依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类。

访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类,其基本结构与实现方法如下。

模式的结构#

访问者模式包含以下主要角色:

  • 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 Visit() ,该操作中的参数类型标识了被访问的具体元素。
  • 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  • 抽象元素(Element)角色:声明一个包含接受操作 Accept() 的接口,被接受的访问者对象作为 Accept() 方法的参数。
  • 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 Accept() 操作,其方法体通常都是 visitor.Visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  • 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

其结构图如图所示:

模式的实现#

访问者模式的实现代码如下:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
class Program
{
static void Main(string[] args)
{
ObjectStructure os=new ObjectStructure();
os.Add(new ConcreteElementA());
os.Add(new ConcreteElementB());
IVisitor visitor=new ConcreteVisitorA();
os.Accept(visitor);
Console.WriteLine("------------------------");
visitor=new ConcreteVisitorB();
os.Accept(visitor);

Console.Read();
}
}


public interface IVisitor
{
void Visit(ConcreteElementA element);
void Visit(ConcreteElementB element);
}


public class ConcreteVisitorA : IVisitor
{
public void Visit(ConcreteElementA element)
{
Console.WriteLine("具体访问者A访问-->"+element.OperationA());
}
public void Visit(ConcreteElementB element)
{
Console.WriteLine("具体访问者A访问-->"+element.OperationB());
}
}


public class ConcreteVisitorB : IVisitor
{
public void Visit(ConcreteElementA element)
{
Console.WriteLine("具体访问者B访问-->"+element.OperationA());
}
public void Visit(ConcreteElementB element)
{
Console.WriteLine("具体访问者B访问-->"+element.OperationB());
}
}


public interface IElement
{
void Accept(IVisitor visitor);
}


public class ConcreteElementA : IElement
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
public String OperationA()
{
return "具体元素A的操作。";
}
}


public class ConcreteElementB : IElement
{
public void Accept(IVisitor visitor)
{
visitor.Visit(this);
}
public String OperationB()
{
return "具体元素B的操作。";
}
}


public class ObjectStructure
{
private List<IElement> list=new List<IElement>();
public void Accept(IVisitor visitor)
{
foreach (var item in list)
{
item.Accept(visitor);
}
}
public void Add(IElement element)
{
list.Add(element);
}
public void Remove(IElement element)
{
list.Remove(element);
}
}

程序的运行结果如下:

1
2
3
4
5
具体访问者A访问
具体访问者A访问

具体访问者B访问
具体访问者B访问

通常在以下情况可以考虑使用访问者(Visitor)模式:

  • 对象结构相对稳定,但其操作算法经常变化的程序。
  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  • 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用:

  • 与“迭代器模式”联用:因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。
    注:现在一般的高级语言都自带的有迭代器,不需要自己实现。

  • 访问者(Visitor)模式同“组合模式”联用:因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式,其结构图如图所示: