訪問者模式(Visitor)
訪問者模式(Visitor),表示一個作用於某物件結構中的各元素的操作。它使你可以在不改變各元素之類別的前提下,定義作用於這些元素的新操作。
訪問者模式(Visitor)比較麻煩,訪問者模式(Visitor)的能力和複雜性是把雙刃劍,只有當你真正需要它的時候,才考慮使用它。
事實上,用訪問者模式(Visitor)的機會其實並不太多,因為我們很難找到資料結構不變化的情況。(比如,人類性別「男女」這樣的資料結構是不會變化的。)
應用場景
訪問者模式(Visitor)適用於資料結構相對穩定的系統。它把資料結構和作用於結構上的操作之間的耦合解脫開,使得操作集合可以相對自由地演化。
目的
訪問者模式(Visitor)的目的,是要把資料從資料結構分離出來。
如果系統的資料結構易於變化,經常要有新的資料物件增加進來,就不適合使用訪問模式。
優缺點
優點
訪問者模式(Visitor)優點是增加新的操作很容易,因為增加新的操作就意味著增加一個新的訪問者。訪問者模式將有關的行為集中到一個訪問者物件中。
ConcreteVisitor具體訪問者,通常可以獨立開發,不必跟ConcreteElement具體元素寫在一起。
缺點
訪問者模式(Visitor)缺點其實也就是使增加新的資料結構變得困難了。
結構
Visitor為該物件結構中ConcreteElement的每一個類別宣告一個Visit操作。ConcreteVisitor具體訪問者,實現每個由Visitor宣告的操作,每個操作實現演算法的一部分,而該演算法片斷乃是對應於結構中物件的類別。ObjectStructure能枚舉它的元素,可以提供一個高層的介面以允許訪問者使用它的元素。Element定義一個Accept操作,它以一個訪問者為參數。ConcreteElementA具體元素,實現Accept操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Client
Visitor 為該物件結構中ConcreteElement的每一個類別宣告一個Visit操作。 (狀態)
+ VisitorConcreteElementA(in 具體元素A: ConcreteElementA)
+ VisitorConcreteElementB(in 具體元素B: ConcreteElementB)
ConcreteVisitorA 具體訪問者,實現每個由Visitor宣告的操作,每個操作實現演算法的一部分,而該演算法片斷乃是對應於結構中物件的類別。
+ VisitorConcreteElementA(in 具體元素A: ConcreteElementA)
ConcreteVisitorB (成功、失敗、戀愛)
+ VisitorConcreteElementB(in 具體元素B: ConcreteElementB)
ObjectStructure 能枚舉它的元素,可以提供一個高層的介面以允許訪問者使用它的元素。(物件結構)
Element 定義一個Accept操作,它以一個訪問者為參數。(人)
+ Accept(in visitor: Visitor)
ConcreteElementA 具體元素,實現Accept操作。(男人)
+ Accept(in visitor: Visitor)
+ OperationA()
ConcreteElementB (女人)
+ Accept(in visitor: Visitor)
+ OperationB()
基本程式碼
Visitor訪問者
Visitor 為該物件結構中ConcreteElement的每一個類別宣告一個Visit操作。
(Visitor為「狀態」類別)
1
2
3
4
5
//訪問者
abstract class Visitor {
public abstract void VisitorConcreteElementA(ConcreteElementA concreteElementA);
public abstract void VisitorConcreteElementB(ConcreteElementB concreteElementB);
}
ConcreteVisitor具體訪問者
ConcreteVisitor 具體訪問者,實現每個由Visitor宣告的操作,每個操作實現演算法的一部分,而該演算法片斷乃是對應於結構中物件的類別。
(具體的ConcreteVisitor就是那些「成功」、「失敗」、「戀愛」等狀態)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//ConcreteVisitorA
class ConcreteVisitorA: Visitor {
public override void VisitorConcreteElementA(ConcreteElementA concreteElementA) {
Console.WriteLine($"{concreteElementA.GetType().Name} 被 {this.GetType().Name}存取");
}
public override void VisitorConcreteElementB(ConcreteElementB concreteElementB) {
Console.WriteLine($"{concreteElementB.GetType().Name} 被 {this.GetType().Name}存取");
}
}
//ConcreteVisitorB
class ConcreteVisitorB: Visitor {
public override void VisitorConcreteElementA(ConcreteElementA concreteElementA) {
Console.WriteLine($"{concreteElementA.GetType().Name} 被 {this.GetType().Name}存取");
}
public override void VisitorConcreteElementB(ConcreteElementB concreteElementB) {
Console.WriteLine($"{concreteElementB.GetType().Name} 被 {this.GetType().Name}存取");
}
}
Element元素
Element 定義一個Accept操作,它以一個訪問者為參數。
(比如,「人」類別)
1
2
3
4
//元素抽象類別
abstract class Element {
public abstract void Accept(Visitor visitor);
}
ConcreteElement具體元素
ConcreteElement 具體元素,實現Accept操作。
(而ConcreteElementA和ConcreteElementB是「男人」和「女人」)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//具體元素A
class ConcreteElementA: Element {
//充分利用雙分派技術,實現處理與資料結構的分離
public override void Accept(Visitor visitor) {
visitor.VisitorConcreteElementA(this);
}
//其他的相關方法
public void OperationA() { }
}
//具體元素B
class ConcreteElementB: Element {
//充分利用雙分派技術,實現處理與資料結構的分離
public override void Accept(Visitor visitor) {
visitor.VisitorConcreteElementB(this);
}
//其他的相關方法
public void OperationB() { }
}
ObjectStructure物件結構
ObjectStructure 能枚舉它的元素,可以提供一個高層的介面以允許訪問者使用它的元素。
(ObjectStructure 就是「物件結構」類別)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Collections.Generic;
class ObjectStructure {
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);
}
}
}
用戶端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void Main()
{
ObjectStructure o = new ObjectStructure();
o.Attach(new ConcreteElementA());
o.Attach(new ConcreteElementB());
ConcreteVisitorA v1 = new ConcreteVisitorA();
ConcreteVisitorB v2 = new ConcreteVisitorB();
o.Accept(v1);
o.Accept(v2);
}
/*
ConcreteElementA 被 ConcreteVisitorA存取
ConcreteElementB 被 ConcreteVisitorA存取
ConcreteElementA 被 ConcreteVisitorB存取
ConcreteElementB 被 ConcreteVisitorB存取
*/
v1.0 男人和女人
例:程式要求實現當男人和女人在不同狀態下的反應。
1
2
3
4
5
6
男人成功的時候,背後多半有一個偉大的女人。
女人成功的時候,背後多半有一個偉大的男人。
男人失敗時,悶頭喝酒,誰也用勸。
失敗女人時,眼淚汪汪,誰也勸不了。
男人戀愛時,凡是不懂也裝懂。
女人戀愛時,遇事懂也裝不懂。
對比這麼多的原因就是因為人類在性別上就只有男人和女人兩類。
1
2
3
4
5
6
7
8
9
public static void Main()
{
Console.WriteLine("男人成功的時候,背後多半有一個偉大的女人。");
Console.WriteLine("女人成功的時候,背後多半有一個偉大的男人。");
Console.WriteLine("男人失敗時,悶頭喝酒,誰也用勸。");
Console.WriteLine("女人失敗時,眼淚汪汪,誰也勸不了。");
Console.WriteLine("男人戀愛時,凡是不懂也裝懂。");
Console.WriteLine("女人戀愛時,遇事懂也裝不懂。");
}
v2.0 男人和女人(簡單的物件導向實現)
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
//人-抽象類別
abstract class Person {
public string Action {get;set;}
//得到結論或反應
public abstract void GetConclusion();
}
//男人
class Man: Person {
//得到結論或反應
public override void GetConclusion() {
switch(Action) {
case "成功":
Console.WriteLine($"{this.GetType().Name}{Action}的時候,背後多半有一個偉大的女人。");
break;
case "失敗":
Console.WriteLine($"{this.GetType().Name}{Action}時,悶頭喝酒,誰也用勸。");
break;
case "戀愛":
Console.WriteLine($"{this.GetType().Name}{Action}時,凡是不懂也裝懂。");
break;
}
}
}
//女人
class Woman: Person {
//得到結論或反應
public override void GetConclusion() {
switch(Action) {
case "成功":
Console.WriteLine($"{this.GetType().Name}{Action}的時候,背後多半有一個偉大的男人。");
break;
case "失敗":
Console.WriteLine($"{this.GetType().Name}{Action}時,眼淚汪汪,誰也勸不了。");
break;
case "戀愛":
Console.WriteLine($"{this.GetType().Name}{Action}時,遇事懂也裝不懂。");
break;
}
}
}
//用戶端
public static void Main()
{
IList<Person> persons = new List<Person>();
Person man1 = new Man();
man1.Action = "成功";
persons.Add(man1);
Person woman1 = new Woman();
woman1.Action = "成功";
persons.Add(woman1);
Person man2 = new Man();
man2.Action = "失敗";
persons.Add(man2);
Person woman2 = new Woman();
woman2.Action = "失敗";
persons.Add(woman2);
Person man3 = new Man();
man3.Action = "戀愛";
persons.Add(man3);
Person woman3 = new Woman();
woman3.Action = "戀愛";
persons.Add(woman3);
foreach(Person p in persons) {
p.GetConclusion();
}
}
/* 執行結果:
Man成功的時候,背後多半有一個偉大的女人。
Woman成功的時候,背後多半有一個偉大的男人。
Man失敗時,悶頭喝酒,誰也用勸。
Woman失敗時,眼淚汪汪,誰也勸不了。
Man戀愛時,凡是不懂也裝懂。
Woman戀愛時,遇事懂也裝不懂。
*/
this.GetType().Name是獲得類別的名稱
v3.0 男人和女人(訪問者模式)
結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Client
狀態
+ 男人反應(in 具體元素A: 男人)
+ 女人反應(in 具體元素B: 女人)
成功
+ 男人反應(in 具體元素A: 男人)
+ 女人反應(in 具體元素B: 女人)
失敗
+ 男人反應(in 具體元素A: 男人)
+ 女人反應(in 具體元素B: 女人)
物件結構
人
+ 接受(in visitor: 狀態)
男人
+ 接受(in Visitor: 狀態)
女人
+ 接受(in Visitor: 狀態)
「狀態」和「人」的抽象類別
1
2
3
4
5
6
7
8
9
10
11
12
13
//狀態 抽象類別
abstract class Action {
//得到男人的結論或反應
public abstract void GetManConcluion(Man concreteElementA);
//得到女人的結論或反應
public abstract void GetWomanConcluion(Woman concreteElementB);
}
//人 抽象類別
abstract class Person {
//接受
public abstract void Accept(Action visitor); //visitor用來獲得「狀態」的物件
}
具體「狀態」類別
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
//成功
class Success: Action {
//得到男人的結論或反應
public override void GetManConcluion(Man concreteElementA) {
Console.WriteLine($"{concreteElementA.GetType().Name} {this.GetType().Name}的時候,背後多半有一個偉大的女人。");
}
//得到女人的結論或反應
public override void GetWomanConcluion(Woman concreteElementB) {
Console.WriteLine($"{concreteElementB.GetType().Name} {this.GetType().Name}的時候,背後多半有一個偉大的男人。");
}
}
//失敗
class Failing: Action {
//得到男人的結論或反應
public override void GetManConcluion(Man concreteElementA) {
Console.WriteLine($"{concreteElementA.GetType().Name} {this.GetType().Name}的時候,悶頭喝酒,誰也用勸。");
}
//得到女人的結論或反應
public override void GetWomanConcluion(Woman concreteElementB) {
Console.WriteLine($"{concreteElementB.GetType().Name} {this.GetType().Name}的時候,眼淚汪汪,誰也勸不了。");
}
}
//戀愛
class Amativeness: Action {
//得到男人的結論或反應
public override void GetManConcluion(Man concreteElementA) {
Console.WriteLine($"{concreteElementA.GetType().Name} {this.GetType().Name}的時候,凡是不懂也裝懂。");
}
//得到女人的結論或反應
public override void GetWomanConcluion(Woman concreteElementB) {
Console.WriteLine($"{concreteElementB.GetType().Name} {this.GetType().Name}的時候,遇事懂也裝不懂。");
}
}
男人、女人類別(具體狀態)
當中用到一種「雙分派」的技術,首先在客戶程式中將具體將態作為參數傳遞給「男人類別」完成了一次分派,然後「男人類別」呼叫作為參數的「具體狀態」中的方法「男人反應」visitor.GetManConcluion(),同時將自己(this)作為參數傳遞進去。這便完成了第二次分派。
「雙分派」意味著得到執行的操作決定於請求的種類和兩個接收者的類型,「接受方法Accept()」就是一個雙分派的操作,它得到執行的操作不僅決定於「狀態類別」的具體狀態,還決定於它存取的「人的類別」。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//男人
class Man: Person {
//接受, visitor用來獲得「狀態」的物件
public override void Accept(Action visitor) {
//首先將"具體狀態"作為參數傳遞給"男人類別"完成了一次分派
//然後"男人類別"呼叫作為參數的「具體狀態」中的方法「男人反應」,同時將自己(this)作為參數傳進去。這便完成了第二次分派
visitor.GetManConcluion(this);
}
}
//女人
class Woman: Person {
//接受, visitor用來獲得「狀態」的物件
public override void Accept(Action visitor) {
visitor.GetWomanConcluion(this);
}
}
物件結構類別
由於總是需要「男人」和「女人」在不同狀態的對比,所以我們需要一個「物件結構」類別來針對不同的「狀態」走遍「男人」和「女人」,得到不同的反應。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//物件結構
class ObjectStructure {
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);
}
}
}
用戶端程式碼
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
public static void Main()
{
//在物件結構中加入要對比的「男人」和「女人」
ObjectStructure o = new ObjectStructure();
o.Attach(new Man());
o.Attach(new Woman());
//成功時的反應
Success v1 = new Success();
o.Display(v1);
//失敗時的反應
Failing v2 = new Failing();
o.Display(v2);
//戀愛時的反應
Amativeness v3 = new Amativeness();
o.Display(v3);
}
/* 執行結果:
Man Success的時候,背後多半有一個偉大的女人。
Woman Success的時候,背後多半有一個偉大的男人。
Man Failing的時候,悶頭喝酒,誰也用勸。
Woman Failing的時候,眼淚汪汪,誰也勸不了。
Man Amativeness的時候,凡是不懂也裝懂。
Woman Amativeness的時候,遇事懂也裝不懂。
*/
這樣做有什麼好處?
這樣做就意味著,如果我們現在要增加「結婚」的狀態來考查「男人」和「女人」的反應,只需要怎麼就可以了?
由於用了雙分派,使得我只需要增加一個「狀態」子類別,就可以在用戶端調用來查看,不需要改動其他任何類別的程式碼。
新增一個結婚狀態
結婚狀態類別
1
2
3
4
5
6
7
8
9
10
11
//結婚狀態
class Marriage: Action {
//得到男人的結論或反應
public override void GetManConcluion(Man concreteElementA) {
Console.WriteLine($"{concreteElementA.GetType().Name} {this.GetType().Name}時,戀愛遊戲終結...");
}
//得到女人的結論或反應
public override void GetWomanConcluion(Woman concreteElementB) {
Console.WriteLine($"{concreteElementB.GetType().Name} {this.GetType().Name}時,愛情長跑路漫慢...。");
}
}
用戶端程式碼
只要加下面一段程式碼就可以完成。
完美的展現了開放-封閉原則
1
2
3
//結婚時的反應
Marriage v4 = new Marriage();
o.Display(v4);
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
public static void Main()
{
//在物件結構中加入要對比的「男人」和「女人」
ObjectStructure o = new ObjectStructure();
o.Attach(new Man());
o.Attach(new Woman());
//成功時的反應
Success v1 = new Success();
o.Display(v1);
//失敗時的反應
Failing v2 = new Failing();
o.Display(v2);
//戀愛時的反應
Amativeness v3 = new Amativeness();
o.Display(v3);
//新增一個结婚状態
//結婚時的反應
Marriage v4 = new Marriage();
o.Display(v4);
}
/* 執行結果:
Man Success的時候,背後多半有一個偉大的女人。
Woman Success的時候,背後多半有一個偉大的男人。
Man Failing的時候,悶頭喝酒,誰也用勸。
Woman Failing的時候,眼淚汪汪,誰也勸不了。
Man Amativeness的時候,凡是不懂也裝懂。
Woman Amativeness的時候,遇事懂也裝不懂。
Man Marriage時,戀愛遊戲終結...
Woman Marriage時,愛情長跑路漫慢...。
*/