還記得警匪片上,匪徒們是怎麼配合實施犯罪的嗎?設計模式
一個團伙在進行盜竊的時候,總有一兩我的在門口把風——若是有什麼風吹草動,則會當即通知裏面的同夥緊急撤退。ide
也許放風的人並不必定認識裏面的每個同夥;測試
而在裏面也許有新來的小弟不認識這個放風的。ui
可是這沒什麼,這個影響不了他們之間的通信,由於他們之間有早已商定好的暗號。spa
呵呵,上面提到的放風者、偷竊者之間的關係就是觀察者模式在現實中的活生生的例子。設計
觀察者(Observer)模式又名發佈-訂閱(Publish/Subscribe)模式。3d
GOF 給觀察者模式以下定義:code
定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新。server
在這裏先提一下面向對象設計的一個重要原則:單一職責原則。對象
系統的每一個對象應該將重點放在問題域中的離散抽象上。
所以理想的狀況下,一個對象只作一件事情。
這樣在開發中也就帶來了諸多的好處:提供了重用性和維護性,也是進行重構的良好的基礎。
所以幾乎全部的設計模式都是基於這個基本的設計原則來的。
觀察者模式的起源應該是在 GUI 和業務數據的處理上,由於如今絕大多數講解觀察者模式的例子都是這一題材。
可是觀察者模式的應用決不只限於此一方面。
在 Subject 這個抽象類中,提供了上面提到的功能,並且存在一個通知方法:notify。
還能夠看到 Subject 和 ConcreteSubject 之間能夠說是使用了模板模式(這個模式真是簡單廣泛到一不當心就用到了)。
這樣當具體目標角色的狀態發生改變,按照約定則會去調用通知方法,在這個方法中則會根據目標角色中註冊的觀察者名單來逐個調用相應的 update 方法來調整觀察者的狀態。
這樣觀察者模式就走完了一個流程。
怎麼才能將業務邏輯和顯示結果的界面很好的分離開?不用問,就是觀察者模式!
看看 JUnit 中觀察者模式的使用代碼:
下面是抽象觀察者角色,JUnit 是採用接口來實現的,這也是通常採用的方式
//下面是咱們的抽象觀察者角色,JUnit 是採用接口來實現的,這也是通常採用的方式。 //能夠看到這裏面定義了四個不一樣的 update 方法,對應四種不一樣的狀態變化 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); }
具體觀察者角色,這裏採用最簡單的 TextUI 下的狀況來講明
//具體觀察者角色,咱們採用最簡單的 TextUI 下的狀況來講明 public class ResultPrinter implements TestListener { //省略好多啊,主要是顯示代碼 // …… //下面就是實現接口 TestListener 的四個方法 //填充方法的行爲很簡單的說 /** * @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; } } }
看下目標角色,因爲JUnit 功能的簡單,只有一個目標——TestResult,所以 JUnit 只有一個具體目標角色。
//好長的代碼,好像沒有重點。去掉了大部分與主題無關的信息 //下面只列出了當 Failures 發生時是怎麼來通知觀察者的 public class TestResult extends Object { //這個是用來存放測試 Failures 的集合 protected Vector fFailures; //這個就是用來存放註冊進來的觀察者的集合 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)); //下面就是通知各個觀察者的 addFailure 方法 for (Enumeration e = cloneListeners().elements(); e.hasMoreElements();) { ((TestListener) e.nextElement()).addFailure(test, t); } } /** * 註冊一個觀察者 */ public synchronized void addListener(TestListener listener) { fListeners.addElement(listener); } /** * 刪除一個觀察者 */ public synchronized void removeListener(TestListener listener) { fListeners.removeElement(listener); } /** * 返回一個觀察者集合的拷貝,固然是爲了防止對觀察者集合的非法方式操做了 * 能夠看到全部使用觀察者集合的地方都經過它 */ private synchronized Vector cloneListeners() { return (Vector) fListeners.clone(); } }
觀察者模式組成所須要的角色在這裏已經全了。不過好像仍是缺點什麼……。呵呵
對!就是它們之間尚未真正的創建聯繫。在 JUnit 中是經過 TestRunner 來做的,而你在具體的系統中能夠靈活掌握。
看一下 TestRunner 中的代碼:
public class TestRunner extends BaseTestRunner { private ResultPrinter fPrinter; public TestResult doRun(Test suite, boolean wait) { //就是在這裏註冊的 result.addListener(fPrinter); ………… } }
GOF 給出瞭如下使用觀察者模式的狀況:
觀察者模式在關於目標角色、觀察者角色通訊的具體實現中,有兩個版本。
這兩種模式的使用,取決於系統設計時的須要。
@成鵬致遠
(blogs:lcw.cnblogs.com)
(email:wwwlllll@126.com)
(qq:552158509)