1. 概述
有時被稱作發(fā)布/訂閱模式,觀察者模式定義了一種一對多的依賴關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象。這個主題對象在狀態(tài)發(fā)生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
2. 解決的問題
將一個系統(tǒng)分割成一個一些類相互協(xié)作的類有一個不好的副作用,那就是需要維護(hù)相關(guān)對象間的一致性。我們不希望為了維持一致性而使各類緊密耦合,這樣會給維護(hù)、擴(kuò)展和重用都帶來不便。觀察者就是解決這類的耦合關(guān)系的。
3. 模式中的角色
3.1 抽象主題(subject):它把所有觀察者對象的引用保存到一個聚集里,每個主題都可以有任何數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象。
3.2 具體主題(concretesubject):將有關(guān)狀態(tài)存入具體觀察者對象;在具體主題內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知。
3.3 抽象觀察者(observer):為所有的具體觀察者定義一個接口,在得到主題通知時更新自己。
3.4 具體觀察者(concreteobserver):實現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題狀態(tài)協(xié)調(diào)。
4. 模式解讀
4.1 觀察者模式的類圖
4.2 觀察者模式的代碼
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
|
/// <summary> /// 抽象主題類 /// </summary> public abstract class subject { private ilist<observer> observers = new list<observer>(); /// <summary> /// 增加觀察者 /// </summary> /// <param name="observer"></param> public void attach(observer observer) { observers.add(observer); } /// <summary> /// 移除觀察者 /// </summary> /// <param name="observer"></param> public void detach(observer observer) { observers.remove(observer); } /// <summary> /// 向觀察者(們)發(fā)出通知 /// </summary> public void notify() { foreach (observer o in observers) { o.update(); } } } /// <summary> /// 抽象觀察者類,為所有具體觀察者定義一個接口,在得到通知時更新自己 /// </summary> public abstract class observer { public abstract void update(); } /// <summary> /// 具體觀察者或具體通知者,將有關(guān)狀態(tài)存入具體觀察者對象;在具體主題的內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知。具體主題角色通常用一個具體子類實現(xiàn)。 /// </summary> public class concretesubject : subject { private string subjectstate; /// <summary> /// 具體觀察者的狀態(tài) /// </summary> public string subjectstate { get { return subjectstate; } set { subjectstate = value; } } } /// <summary> /// 具體觀察者,實現(xiàn)抽象觀察者角色所要求的更新接口,已是本身狀態(tài)與主題狀態(tài)相協(xié)調(diào) /// </summary> public class concreteobserver : observer { private string observerstate; private string name; private concretesubject subject; /// <summary> /// 具體觀察者用一個具體主題來實現(xiàn) /// </summary> public concretesubject subject { get { return subject; } set { subject = value; } } public concreteobserver(concretesubject subject, string name) { this .subject = subject; this .name = name; } /// <summary> /// 實現(xiàn)抽象觀察者中的更新操作 /// </summary> public override void update() { observerstate = subject.subjectstate; console.writeline( "the observer's state of {0} is {1}" , name, observerstate); } } |
4.3 客戶端代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class program { static void main( string [] args) { // 具體主題角色通常用具體自來來實現(xiàn) concretesubject subject = new concretesubject(); subject.attach( new concreteobserver(subject, "observer a" )); subject.attach( new concreteobserver(subject, "observer b" )); subject.attach( new concreteobserver(subject, "observer c" )); subject.subjectstate = "ready" ; subject.notify(); console.read(); } } |
運(yùn)行結(jié)果
5. 模式總結(jié)
5.1 優(yōu)點
5.1.1 觀察者模式解除了主題和具體觀察者的耦合,讓耦合的雙方都依賴于抽象,而不是依賴具體。從而使得各自的變化都不會影響另一邊的變化。
5.2 缺點
5.2.1 依賴關(guān)系并未完全解除,抽象通知者依舊依賴抽象的觀察者。
5.3 適用場景
5.3.1 當(dāng)一個對象的改變需要給變其它對象時,而且它不知道具體有多少個對象有待改變時。
5.3.2 一個抽象某型有兩個方面,當(dāng)其中一個方面依賴于另一個方面,這時用觀察者模式可以將這兩者封裝在獨(dú)立的對象中使它們各自獨(dú)立地改變和復(fù)用。
6. 模式引申,應(yīng)用c#中的事件委托來徹底解除通知者和觀察者之間的耦合。
6.1 關(guān)于委托的定義:委托是一種引用方法的類型。一旦為委托分配了方法,委托將與該方法有相同的行為。委托方法可以像其它任何方法一樣,具有參數(shù)和返回值。委托可以看作是對函數(shù)(方法)的的抽象,是函數(shù)的“類”,委托的實例代表一個(或多個)具體的函數(shù),它可以是多播的。
6.2 關(guān)于事件:事件基于委托,為委托提供了一種發(fā)布/訂閱機(jī)制。事件的訂閱與取消與我們剛才講的觀察者模式中的訂閱與取消類似,只是表現(xiàn)形式有所不同。在觀察者模式中,訂閱使用方法attach()來進(jìn)行;在事件的訂閱中使用“+=”。類似地,取消訂閱在觀察者模式中用dettach(),而事件的取消用“-=”。
7. 下面例子分別用觀察者模式,事件機(jī)制來實現(xiàn)
7.1 實例描述:客戶支付了訂單款項,這時財務(wù)需要開具發(fā)票,出納需要記賬,配送員需要配貨。
7.2 觀察者模式的實現(xiàn)
7.2.1 類圖
7.2.2 代碼實現(xiàn)
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
/// <summary> /// 抽象觀察者 /// </summary> public interface isubject { void notify(); } /// <summary> /// 工作崗位,作為這里的觀察者的抽象 /// </summary> public abstract class jobstation { public abstract void update(); } /// <summary> /// 具體主題,這里是客戶 /// </summary> public class customer : isubject { private string customerstate; private ilist<jobstation> observers = new list<jobstation>(); /// <summary> /// 增加觀察者 /// </summary> /// <param name="observer"></param> public void attach(jobstation observer) { this .observers.add(observer); } /// <summary> /// 移除觀察者 /// </summary> /// <param name="observer"></param> public void detach(jobstation observer) { this .observers.remove(observer); } /// <summary> /// 客戶狀態(tài) /// </summary> public string customerstate { get { return customerstate; } set { customerstate = value; } } public void notify() { foreach (jobstation o in observers) { o.update(); } } } /// <summary> /// 會計 /// </summary> public class accountant : jobstation { private string accountantstate; private customer customer; public accountant(customer customer) { this .customer = customer; } /// <summary> /// 更新狀態(tài) /// </summary> public override void update() { if (customer.customerstate == "已付款" ) { console.writeline( "我是會計,我來開具發(fā)票。" ); accountantstate = "已開發(fā)票" ; } } } /// <summary> /// 出納 /// </summary> public class cashier : jobstation { private string cashierstate; private customer customer; public cashier(customer customer) { this .customer = customer; } public override void update() { if (customer.customerstate == "已付款" ) { console.writeline( "我是出納員,我給登記入賬。" ); cashierstate = "已入賬" ; } } } /// <summary> /// 配送員 /// </summary> public class dilliveryman : jobstation { private string dillivierymanstate; private customer customer; public dilliveryman(customer customer) { this .customer = customer; } public override void update() { if (customer.customerstate == "已付款" ) { console.writeline( "我是配送員,我來發(fā)貨。" ); dillivierymanstate = "已發(fā)貨" ; } } } |
7.2.3 客戶端代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class program { static void main(string[] args) { customer subject = new customer(); subject.attach(new accountant(subject)); subject.attach(new cashier(subject)); subject.attach(new dilliveryman(subject)); subject.customerstate = "已付款"; subject.notify(); console.read(); } } |
運(yùn)行結(jié)果:
我是會計,我來開具發(fā)票。
我是出納員,我給登記入賬。
我是配送員,我來發(fā)貨。
7.3 事件實現(xiàn)
7.3.1 類圖
通過類圖來看,觀察者和主題之間已經(jīng)不存在任何依賴關(guān)系了。
7.3.2 代碼實現(xiàn)
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
|
/// <summary> /// 抽象主題 /// </summary> public interface isubject { void notify(); } /// <summary> /// 聲明委托 /// </summary> public delegate void customereventhandler(); /// <summary> /// 具體主題 /// </summary> public class customer : isubject { private string customerstate; // 聲明一個委托事件,類型為 customereventhandler public event customereventhandler update; public void notify() { if (update != null ) { // 使用事件來通知給訂閱者 update(); } } public string customerstate { get { return customerstate; } set { customerstate = value; } } } /// <summary> /// 財務(wù),已經(jīng)不需要實現(xiàn)抽象的觀察者類,并且不用引用具體的主題 /// </summary> public class accountant { private string accountantstate; public accountant() { } /// <summary> /// 開發(fā)票 /// </summary> public void giveinvoice() { console.writeline( "我是會計,我來開具發(fā)票。" ); accountantstate = "已開發(fā)票" ; } } /// <summary> /// 出納,已經(jīng)不需要實現(xiàn)抽象的觀察者類,并且不用引用具體的主題 /// </summary> public class cashier { private string cashierstate; public void recoded() { console.writeline( "我是出納員,我給登記入賬。" ); cashierstate = "已入賬" ; } } /// <summary> /// 配送員,已經(jīng)不需要實現(xiàn)抽象的觀察者類,并且不用引用具體的主題 /// </summary> public class dilliveryman { private string dillivierymanstate; public void dilliver() { console.writeline( "我是配送員,我來發(fā)貨。" ); dillivierymanstate = "已發(fā)貨" ; } } |
7.3.3 客戶端代碼
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
|
class program { static void main( string [] args) { customer subject = new customer(); accountant accountant = new accountant(); cashier cashier = new cashier(); dilliveryman dilliveryman = new dilliveryman(); // 注冊事件 subject.update += accountant.giveinvoice; subject.update += cashier.recoded; subject.update += dilliveryman.dilliver; /* * 以上寫法也可以用下面代碼來替換 subject.update += new customereventhandler(accountant.giveinvoice); subject.update += new customereventhandler(cashier.recoded); subject.update += new customereventhandler(dilliveryman.dilliver); */ subject.customerstate = "已付款" ; subject.notify(); console.read(); } } |
運(yùn)行結(jié)果
我是會計,我來開具發(fā)票。
我是出納員,我給登記入賬。
我是配送員,我來發(fā)貨。