每日一得

          不求多得,只求一得 about java,hibernate,spring,design,database,Ror,ruby,快速開發(fā)
          最近關(guān)心的內(nèi)容:SSH,seam,flex,敏捷,TDD
          本站的官方站點(diǎn)是:顛覆軟件

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            220 隨筆 :: 9 文章 :: 421 評(píng)論 :: 0 Trackbacks
          <2006年8月>
          303112345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          常用鏈接

          留言簿(23)

          隨筆分類(240)

          隨筆檔案(219)

          文章分類(9)

          文章檔案(9)

          收藏夾(15)

          java link

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          一、 引子

          還記得警匪片上,匪徒們是怎么配合實(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ì)你有所幫助。

          posted on 2006-08-29 18:08 Alex 閱讀(310) 評(píng)論(0)  編輯  收藏 所屬分類: design
          主站蜘蛛池模板: 昌平区| 仁寿县| 衡山县| 阿拉善盟| 焉耆| 江北区| 满城县| 阳山县| 吴桥县| 盘锦市| 太白县| 水城县| 宜川县| 大名县| 额济纳旗| 柳林县| 辽中县| 镶黄旗| 民和| 平安县| 隆回县| 开鲁县| 同江市| 二手房| 禹州市| 金秀| 舞阳县| 普洱| 博白县| 巴里| 略阳县| 东至县| 施甸县| 乌拉特前旗| 星座| 哈密市| 灵石县| 平定县| 辽宁省| 海伦市| 和顺县|