一、
引子
還記得警匪片上,匪徒們是怎么配合實(shí)施犯罪的嗎?一個(gè)團(tuán)伙在進(jìn)行盜竊的時(shí)候,總
???
有一兩個(gè)人在門口把風(fēng)——如果有什么風(fēng)吹草動(dòng),則會(huì)立即通知里面的同伙緊急撤退。也許放風(fēng)的人并不一定認(rèn)識(shí)里面的每一個(gè)同伙;而在里面也許有新來(lái)的小弟不認(rèn)識(shí)這個(gè)放風(fēng)的。但是這沒(méi)什么,這個(gè)影響不了他們之間的通訊,因?yàn)樗麄冎g有早已商定好的暗號(hào)。
呵呵,上面提到的放風(fēng)者、偷竊者之間的關(guān)系就是觀察者模式在現(xiàn)實(shí)中的活生生的例子。
?
二、
定義與結(jié)構(gòu)
觀察者(
Observer
)模式又名發(fā)布
-
訂閱(
Publish/Subscribe
)模式。
GOF
給觀察者模式如下定義:定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。
在這里先講一下面向?qū)ο笤O(shè)計(jì)的一個(gè)重要原則——單一職責(zé)原則。因此系統(tǒng)的每個(gè)對(duì)象應(yīng)該將重點(diǎn)放在問(wèn)題域中的離散抽象上。因此理想的情況下,一個(gè)對(duì)象只做一件事情。這樣在開發(fā)中也就帶來(lái)了諸多的好處:提供了重用性和維護(hù)性,也是進(jìn)行重構(gòu)的良好的基礎(chǔ)。
因此幾乎所有的設(shè)計(jì)模式都是基于這個(gè)基本的設(shè)計(jì)原則來(lái)的。觀察者模式的起源我覺得應(yīng)該是在
GUI
和業(yè)務(wù)數(shù)據(jù)的處理上,因?yàn)楝F(xiàn)在絕大多數(shù)講解觀察者模式的例子都是這一題材。但是觀察者模式的應(yīng)用決不僅限于此一方面。
下面我們就來(lái)看看觀察者模式的組成部分。
1)
???????
抽象目標(biāo)角色(
Subject
):目標(biāo)角色知道它的觀察者,可以有任意多個(gè)觀察者觀察同一個(gè)目標(biāo)。并且提供注冊(cè)和刪除觀察者對(duì)象的接口。目標(biāo)角色往往由抽象類或者接口來(lái)實(shí)現(xiàn)。
2)
???????
抽象觀察者角色(
Observer
):為那些在目標(biāo)發(fā)生改變時(shí)需要獲得通知的對(duì)象定義一個(gè)更新接口。抽象觀察者角色主要由抽象類或者接口來(lái)實(shí)現(xiàn)。
3)
???????
具體目標(biāo)角色(
Concrete Subject
):將有關(guān)狀態(tài)存入各個(gè)
Concrete Observer
對(duì)象。當(dāng)它的狀態(tài)發(fā)生改變時(shí)
,
向它的各個(gè)觀察者發(fā)出通知。
4)
???????
具體觀察者角色(
Concrete Observer
):存儲(chǔ)有關(guān)狀態(tài),這些狀態(tài)應(yīng)與目標(biāo)的狀態(tài)保持一致。實(shí)現(xiàn)
Observer
的更新接口以使自身狀態(tài)與目標(biāo)的狀態(tài)保持一致。在本角色內(nèi)也可以維護(hù)一個(gè)指向
Concrete Subject
對(duì)象的引用。
放上觀察者模式的類圖,這樣能將關(guān)系清晰的表達(dá)出來(lái)。
??????
可以看得出來(lái),在
Subject
這個(gè)抽象類中,提供了上面提到的功能,而且存在一個(gè)通知方法:
notify
。還可以看出來(lái)
Subject
和
ConcreteSubject
之間可以說(shuō)是使用了模板模式(這個(gè)模式真是簡(jiǎn)單普遍到一不小心就用到了)。
??????
這樣當(dāng)具體目標(biāo)角色的狀態(tài)發(fā)生改變,按照約定則會(huì)去調(diào)用通知方法,在這個(gè)方法中則會(huì)根據(jù)目標(biāo)角色中注冊(cè)的觀察者名單來(lái)逐個(gè)調(diào)用相應(yīng)的
update
方法來(lái)調(diào)整觀察者的狀態(tài)。這樣觀察者模式就走完了一個(gè)流程。
??????
在下面的例子中會(huì)更深刻的體驗(yàn)到這個(gè)流程的。
?
三、
舉例
觀察者模式是我在《
JUnit
源代碼分析》中遺留的一個(gè)模式,因此這里將采用
JUnit
來(lái)作為例子。
JUnit
為用戶提供了三種不同的測(cè)試結(jié)果顯示界面,以后還可能會(huì)有其它方式的現(xiàn)實(shí)界面……。怎么才能將測(cè)試的業(yè)務(wù)邏輯和顯示結(jié)果的界面很好的分離開?不用問(wèn),就是觀察者模式!
下面我們來(lái)看看
JUnit
中觀察者模式的使用代碼:
//
下面是我們的抽象觀察者角色,
JUnit
是采用接口來(lái)實(shí)現(xiàn)的,這也是一般采用的方式。
//
可以看到這里面定義了四個(gè)不同的
update
方法,對(duì)應(yīng)四種不同的狀態(tài)變化
public interface TestListener {
?????? /**
?
????
?* An error occurred.
?
????
?*/
?????? public void addError(Test test, Throwable t);
?????? /**
?
????
?* A failure occurred.
?
????
?*/
?
???? public void addFailure(Test test, AssertionFailedError t);?
?????? /**
??????
?* A test ended.
??????
?*/
?
???? public void endTest(Test test);
?????? /**
??????
?* A test started.
??????
?*/
?????? public void startTest(Test test);
}
?
//
具體觀察者角色,我們采用最簡(jiǎn)單的
TextUI
下的情況來(lái)說(shuō)明(
AWT
,
Swing
對(duì)于整天做
Web
應(yīng)用的人來(lái)說(shuō),已經(jīng)很陌生了)
public class ResultPrinter implements TestListener {
?????? //
省略好多啊,主要是顯示代碼
……
?????? //
下面就是實(shí)現(xiàn)接口
TestListener
的四個(gè)方法
?????? //
填充方法的行為很簡(jiǎn)單的說(shuō)
?????? /**
??????
?* @see junit.framework.TestListener#addError(Test, Throwable)
??????
?*/
?????? public void addError(Test test, Throwable t) {
????????????? getWriter().print("E");
?????? }
?????? /**
??????
?* @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
??????
?*/
?????? public void addFailure(Test test, AssertionFailedError t) {
????????????? getWriter().print("F");
?????? }
?????? /**
??????
?* @see junit.framework.TestListener#endTest(Test)
??????
?*/
?????? public void endTest(Test test) {
?????? }
?????? /**
??????
?* @see junit.framework.TestListener#startTest(Test)
??????
?*/
?????? public void startTest(Test test) {
????????????? getWriter().print(".");
????????????? if (fColumn++ >= 40) {
???????????????????? getWriter().println();
???????????????????? fColumn= 0;
????????????? }
?????? }
}
?
來(lái)看下我們的目標(biāo)角色,隨便說(shuō)下,由于
JUnit
功能的簡(jiǎn)單,只有一個(gè)目標(biāo)——
TestResult
,因此
JUnit
只有一個(gè)具體目標(biāo)角色。
//
好長(zhǎng)的代碼,好像沒(méi)有重點(diǎn)。去掉了大部分與主題無(wú)關(guān)的信息
//
下面只列出了當(dāng)
Failures
發(fā)生時(shí)是怎么來(lái)通知觀察者的
public class TestResult extends Object {
?????? //
這個(gè)是用來(lái)存放測(cè)試
Failures
的集合
protected Vector fFailures;
//
這個(gè)就是用來(lái)存放注冊(cè)進(jìn)來(lái)的觀察者的集合
?????? protected Vector fListeners;
?
?????? public TestResult() {
????????????? fFailures= new Vector();
????????????? fListeners= new Vector();
?????? }
?????? /**
??????
?* Adds a failure to the list of failures. The passed in exception
??????
?* caused the failure.
??????
?*/
?????? public synchronized void addFailure(Test test, AssertionFailedError t) {
????????????? fFailures.addElement(new TestFailure(test, t));
????????????? //
下面就是通知各個(gè)觀察者的
addFailure
方法
????????????? for (Enumeration e= cloneListeners().elements(); e.hasMoreElements(); ) {
???????????????????? ((TestListener)e.nextElement()).addFailure(test, t);
????????????? }
?????? }
?????? /**
??????
?*
注冊(cè)一個(gè)觀察者
??????
?*/
?????? public synchronized void addListener(TestListener listener) {
????????????? fListeners.addElement(listener);
?????? }
?????? /**
??????
?*
刪除一個(gè)觀察者
??????
?*/
?????? public synchronized void removeListener(TestListener listener) {
????????????? fListeners.removeElement(listener);
?????? }
?????? /**
??????
?*
返回一個(gè)觀察者集合的拷貝,當(dāng)然是為了防止對(duì)觀察者集合的非法方式操作了
???? *
可以看到所有使用觀察者集合的地方都通過(guò)它
??????
?*/
?????? private synchronized Vector cloneListeners() {
????????????? return (Vector)fListeners.clone();
?????? }
??????
……
}
?
嗯,觀察者模式組成所需要的角色在這里已經(jīng)全了。不過(guò)好像還是缺點(diǎn)什么……。呵呵,對(duì)!就是它們之間還沒(méi)有真正的建立聯(lián)系。在
JUnit
中是通過(guò)
TestRunner
來(lái)作的,而你在具體的系統(tǒng)中可以靈活掌握。
看一下
TestRunner
中的代碼:
public class TestRunner extends BaseTestRunner {
?????? private ResultPrinter fPrinter;
public TestResult doRun(Test suite, boolean wait) {
//
就是在這里注冊(cè)的
????????????? result.addListener(fPrinter);
……
?
四、
使用情況
GOF
給出了以下使用觀察者模式的情況:
1)
???????
當(dāng)一個(gè)抽象模型有兩個(gè)方面
,
其中一個(gè)方面依賴于另一方面。將這二者封裝在獨(dú)立的對(duì)象中以使它們可以各自獨(dú)立地改變和復(fù)用。
2)
???????
當(dāng)對(duì)一個(gè)對(duì)象的改變需要同時(shí)改變其它對(duì)象
,
而不知道具體有多少對(duì)象有待改變。
3)
???????
當(dāng)一個(gè)對(duì)象必須通知其它對(duì)象,而它又不能假定其它對(duì)象是誰(shuí)。換言之
,
你不希望這些對(duì)象是緊密耦合的。
其實(shí)觀察者模式同前面講過(guò)的橋梁、策略有著共同的使用環(huán)境:將變化獨(dú)立封裝起來(lái),以達(dá)到最大的重用和解耦。觀察者與后兩者不同的地方在于,觀察者模式中的目標(biāo)和觀察者的變化不是獨(dú)立的,而是有著某些聯(lián)系。
?
五、
我推你拉
觀
察者模式在關(guān)于目標(biāo)角色、觀察者角色通信的具體實(shí)現(xiàn)中,有兩個(gè)版本。一種情況便是目標(biāo)角色在發(fā)生變化后,僅僅告訴觀察者角色“我變化了”;觀察者角色如果
想要知道具體的變化細(xì)節(jié),則就要自己從目標(biāo)角色的接口中得到。這種模式被很形象的稱為:拉模式——就是說(shuō)變化的信息是觀察者角色主動(dòng)從目標(biāo)角色中“拉”出
來(lái)的。
還有一種方法,那就是我目標(biāo)角色“服務(wù)一條龍”,通知你發(fā)生變化的同時(shí),通過(guò)一個(gè)參數(shù)將變化的細(xì)節(jié)傳遞到觀察者角色中去。這就是“推模式”——管你要不要,先給你啦。
這兩種模式的使用,取決于系統(tǒng)設(shè)計(jì)時(shí)的需要。如果目標(biāo)角色比較復(fù)雜,并且觀察者角色進(jìn)行更新時(shí)必須得到一些具體變化的信息,則“推模式”比較合適。如果目標(biāo)角色比較簡(jiǎn)單,則“拉模式”就很合適啦。
?
六、
總結(jié)
大概的介紹了下觀察者模式。希望能對(duì)你有所幫助。