【行為型模式十八】觀察者模式(Observer)

1 場景問題#

1.1 訂閱報紙的過程##

來考慮實際生活中訂閱報紙的過程,這里簡單總結(jié)了一下,訂閱報紙的基本流程如下:

首先按照自己的需要選擇合適的報紙,具體的報刊雜志目錄可以從郵局獲取;

選擇好后,就到郵局去填寫訂閱單,同時交上所需的費用;

至此,就完成了報紙的訂閱過程,接下去的就是耐心等候,報社會按照出報時間推出報紙,然后報紙會被送到每個訂閱人的手里。

畫個圖來描述上述過程,如圖所示:

訂閱報紙的過程示意圖

雖然看起來訂閱者是直接跟郵局在打交道,但實際上,訂閱者的訂閱數(shù)據(jù)是會被郵局傳遞到報社的,當(dāng)報社出版了報紙,報社會按照訂閱信息把報紙交給郵局,然后由郵局來代為發(fā)送到訂閱者的手中。所以在整個過程中,郵局只不過起到一個中轉(zhuǎn)的作用,為了簡單,我們?nèi)サ羿]局,讓訂閱者直接和報社交互,如圖所示:

簡化的訂閱報紙過程示意圖

1.2 訂閱報紙的問題##

在上述過程中,訂閱者在完成訂閱后,最關(guān)心的問題就是何時能收到新出的報紙。幸好在現(xiàn)實生活中,報紙都是定期出版,這樣發(fā)放到訂閱者手中也基本上有一個大致的時間范圍,差不多到時間了,訂閱者就會看看郵箱,查收新的報紙。

要是報紙出版的時間不固定呢?

那訂閱者就麻煩了,如果訂閱者想要第一時間閱讀到新報紙,恐怕只能天天守著郵箱了,這未免也太痛苦了吧。

繼續(xù)引申一下,用類來描述上述的過程,描述如下:

訂閱者類向出版者類訂閱報紙,很明顯不會只有一個訂閱者訂閱報紙,訂閱者類可以有很多;當(dāng)出版者類出版新報紙的時候,多個訂閱者類如何知道呢?還有訂閱者類如何得到新報紙的內(nèi)容呢?

把上面的問題對比描述一下:

進(jìn)一步抽象描述這個問題:當(dāng)一個對象的狀態(tài)發(fā)生改變的時候,如何讓依賴于它的所有對象得到通知,并進(jìn)行相應(yīng)的處理呢?

該如何解決這樣的問題?

2 解決方案#

2.1 觀察者模式來解決##

用來解決上述問題的一個合理的解決方案就是觀察者模式。那么什么是觀察者模式呢?

  1. 觀察者模式定義
  1. 應(yīng)用觀察者模式來解決的思路

在前面描述的訂閱報紙的例子里面,對于報社來說,在一開始,它并不清楚究竟有多少個訂閱者會來訂閱報紙,因此,報社需要維護(hù)一個訂閱者的列表,這樣當(dāng)報社出版報紙的時候,才能夠把報紙發(fā)放到所有的訂閱者手中。對于訂閱者來說,訂閱者也就是看報的讀者,多個訂閱者會訂閱同一份報紙。

這就出現(xiàn)了一個典型的一對多的對象關(guān)系,一個報紙對象,會有多個訂閱者對象來訂閱;當(dāng)報紙出版的時候,也就是報紙對象改變的時候,需要通知所有的訂閱者對象。那么怎么來建立并維護(hù)這樣的關(guān)系呢?

觀察者模式可以處理這種問題,觀察者模式把這多個訂閱者稱為觀察者:Observer,多個觀察者觀察的對象被稱為目標(biāo):Subject。

一個目標(biāo)可以有任意多個觀察者對象,一旦目標(biāo)的狀態(tài)發(fā)生了改變,所有注冊的觀察者都會得到通知,然后各個觀察者會對通知作出相應(yīng)的響應(yīng),執(zhí)行相應(yīng)的業(yè)務(wù)功能處理,并使自己的狀態(tài)和目標(biāo)對象的狀態(tài)保持一致。

2.2 模式結(jié)構(gòu)和說明##

觀察者模式結(jié)構(gòu)如圖所示:

觀察者模式結(jié)構(gòu)示意圖

Subject:目標(biāo)對象,通常具有如下功能:

     1. 一個目標(biāo)可以被多個觀察者觀察;

     2. 目標(biāo)提供對觀察者注冊和退訂的維護(hù);

     3. 當(dāng)目標(biāo)的狀態(tài)發(fā)生變化時,目標(biāo)負(fù)責(zé)通知所有注冊的、有效的觀察者;

Observer:定義觀察者的接口,提供目標(biāo)通知時對應(yīng)的更新方法,這個更新方法進(jìn)行相應(yīng)的業(yè)務(wù)處理,可以在這個方法里面回調(diào)目標(biāo)對象,以獲取目標(biāo)對象的數(shù)據(jù)。

ConcreteSubject:具體的目標(biāo)實現(xiàn)對象,用來維護(hù)目標(biāo)狀態(tài),當(dāng)目標(biāo)對象的狀態(tài)發(fā)生改變時,通知所有注冊有效的觀察者,讓觀察者執(zhí)行相應(yīng)的處理。

ConcreteObserver:觀察者的具體實現(xiàn)對象,用來接收目標(biāo)的通知,并進(jìn)行相應(yīng)的后續(xù)處理,比如更新自身的狀態(tài)以保持和目標(biāo)的相應(yīng)狀態(tài)一致。

2.3 觀察者模式示例代碼##

  1. 先來看看目標(biāo)對象的定義,示例代碼如下:
/**
 * 目標(biāo)對象,它知道觀察它的觀察者,并提供注冊和刪除觀察者的接口
 */
public class Subject {
    /**
     * 用來保存注冊的觀察者對象
     */
    private List<Observer> observers = new ArrayList<Observer>();
    /**
     * 注冊觀察者對象
     * @param observer 觀察者對象
     */
    public void attach(Observer observer) {
       observers.add(observer);
    }
    /**
     * 刪除觀察者對象
     * @param observer 觀察者對象
     */
    public void detach(Observer observer) {
       observers.remove(observer);
    }
    /**
     * 通知所有注冊的觀察者對象
     */
    protected void notifyObservers() {
       for(Observer observer : observers){
           observer.update(this);
       }
    }
}
  1. 接下來看看具體的目標(biāo)對象,示例代碼如下:
/**
 * 具體的目標(biāo)對象,負(fù)責(zé)把有關(guān)狀態(tài)存入到相應(yīng)的觀察者對象,
 * 并在自己狀態(tài)發(fā)生改變時,通知各個觀察者
 */
public class ConcreteSubject extends Subject {
    /**
     * 示意,目標(biāo)對象的狀態(tài)
     */
    private String subjectState;
    public String getSubjectState() {
       return subjectState;
    }
    public void setSubjectState(String subjectState) {
       this.subjectState = subjectState;
       //狀態(tài)發(fā)生了改變,通知各個觀察者
       this.notifyObservers();
    }
}
  1. 再來看看觀察者的接口定義,示例代碼如下:
/**
 * 觀察者接口,定義一個更新的接口給那些在目標(biāo)發(fā)生改變的時候被通知的對象
 */
public interface Observer {
    /**
     * 更新的接口
     * @param subject 傳入目標(biāo)對象,好獲取相應(yīng)的目標(biāo)對象的狀態(tài)
     */
    public void update(Subject subject);
}
  1. 接下來看看觀察者的具體實現(xiàn)示意,示例代碼如下:
/**
 * 具體觀察者對象,實現(xiàn)更新的方法,使自身的狀態(tài)和目標(biāo)的狀態(tài)保持一致
 */
public class ConcreteObserver implements Observer {
    /**
     * 示意,觀者者的狀態(tài)
     */
    private String observerState;
  
    public void update(Subject subject) {
       // 具體的更新實現(xiàn)
       //這里可能需要更新觀察者的狀態(tài),使其與目標(biāo)的狀態(tài)保持一致
       observerState = ((ConcreteSubject)subject).getSubjectState();
    }
}

2.4 使用觀察者模式實現(xiàn)示例##

要使用觀察者模式來實現(xiàn)示例,那就按照前面講述的實現(xiàn)思路,把報紙對象當(dāng)作目標(biāo),然后訂閱者當(dāng)做觀察者,就可以實現(xiàn)出來了。

使用觀察者模式來實現(xiàn)示例的結(jié)構(gòu)如圖所示:

使用觀察者模式來實現(xiàn)示例的結(jié)構(gòu)示意圖
  1. 被觀察的目標(biāo)

在前面描述的訂閱報紙的例子里面,多個訂閱者都是在觀察同一個報社對象,這個報社對象就是被觀察的目標(biāo)。這個目標(biāo)的接口應(yīng)該有些什么方法呢?還是從實際入手去想,看看報社都有些什么功能。報社最基本有如下的功能:

注冊訂閱者,也就是說很多個人來訂報紙,報社肯定要有相應(yīng)的記錄才行;

出版報紙,這個是報社的主要工作;

發(fā)行報紙,也就是要把出版的報紙發(fā)送到訂閱者手中;

退訂報紙,當(dāng)訂閱者不想要繼續(xù)訂閱了,可以取消訂閱;

上面這些功能是報社最最基本的功能,當(dāng)然,報社還有很多別的功能,為了簡單起見,這里就不再去描述了。因此報社這個目標(biāo)的接口也應(yīng)該實現(xiàn)上述功能,把他們定義在目標(biāo)接口里面,示例代碼如下:

/**
 * 目標(biāo)對象,作為被觀察者
 */
public class Subject {
    /**
     * 用來保存注冊的觀察者對象,也就是報紙的訂閱者
     */
    private List<Observer> readers = new ArrayList<Observer>();

    /**
     * 報紙的讀者需要先向報社訂閱,先要注冊
     * @param reader 報紙的讀者
     * @return 是否注冊成功
     */
    public void attach(Observer reader) {
       readers.add(reader);
    }
    /**
     * 報紙的讀者可以取消訂閱
     * @param reader 報紙的讀者
     * @return 是否取消成功
     */
    public void detach(Observer reader) {
       readers.remove(reader);
    }
    /**
     * 當(dāng)每期報紙印刷出來后,就要迅速主動的被送到讀者的手中,
     * 相當(dāng)于通知讀者,讓他們知道
     */
    protected void notifyObservers() {
       for(Observer reader : readers){
           reader.update(this);
       }
    }
}

細(xì)心的朋友可能會發(fā)現(xiàn),這個對象并沒有定義出版報紙的功能,這是為了讓這個對象更加通用,這個功能還是有的,放到具體報紙類里面去了,下面就來具體的看看具體的報紙類的實現(xiàn)。

為了演示簡單,在這個實現(xiàn)類里面增添一個屬性,用它來保存報紙的內(nèi)容,然后增添一個方法來修改這個屬性,修改這個屬性就相當(dāng)于出版了新的報紙,并且同時通知所有的訂閱者。示例代碼如下:

/**
 * 報紙對象,具體的目標(biāo)實現(xiàn)
 */
public class NewsPaper extends Subject{
    /**
     * 報紙的具體內(nèi)容
     */
    private String content;
    /**
     * 獲取報紙的具體內(nèi)容
     * @return 報紙的具體內(nèi)容
     */
    public String getContent() {
       return content;
    }

    /**
     * 示意,設(shè)置報紙的具體內(nèi)容,相當(dāng)于要出版報紙了
     * @param content 報紙的具體內(nèi)容
     */
    public void setContent(String content) {
       this.content = content;
       //內(nèi)容有了,說明又出報紙了,那就通知所有的讀者
       notifyObservers();
    }
}
  1. 觀察者

目標(biāo)定義好過后,接下來把觀察者抽象出來,看看它應(yīng)該具有什么功能。分析前面的描述,發(fā)現(xiàn)觀察者只要去郵局注冊了過后,就是等著接收報紙就好了,沒有什么其它的功能。那么就把這個接收報紙的功能抽象成為更新的方法,從而定義出觀察者接口來,示例代碼如下:

/**
 * 觀察者,比如報紙的讀者
 */
public interface Observer {
    /**
     * 被通知的方法
     * @param subject 具體的目標(biāo)對象,可以獲取報紙的內(nèi)容
     */
    public void update(Subject subject);
}

定義好了觀察者的接口過后,該來想想如何實現(xiàn)了。具體的觀察者需要實現(xiàn):在收到被通知的內(nèi)容后,自身如何進(jìn)行相應(yīng)處理的功能。為了演示的簡單,收到報紙內(nèi)容過后,簡單的輸出一下,表示收到了就行了。

定義一個簡單的觀察者實現(xiàn),示例代碼如下:

/**
 * 真正的讀者,為了簡單就描述一下姓名
 */
public class Reader implements Observer{
    /**
     * 讀者的姓名
     */
    private String name;

    public void update(Subject subject) {
       //這是采用拉的方式
       System.out.println(name+"收到報紙了,閱讀先。內(nèi)容是==="+((NewsPaper)subject).getContent());
    }

    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
}
  1. 使用觀察者模式

前面定義好了觀察者和觀察的目標(biāo),那么如何使用它們呢?

那就寫個客戶端,在客戶端里面,先創(chuàng)建好一個報紙,作為被觀察的目標(biāo),然后多創(chuàng)建幾個讀者作為觀察者,當(dāng)然需要把這些觀察者都注冊到目標(biāo)里面去,接下來就可以出版報紙了,具體的示例代碼如下:

public class Client {
    public static void main(String[] args) {
       //創(chuàng)建一個報紙,作為被觀察者
       NewsPaper subject = new NewsPaper();
       //創(chuàng)建閱讀者,也就是觀察者
       Reader reader1 = new Reader();
       reader1.setName("張三");
     
       Reader reader2 = new Reader();
       reader2.setName("李四");
     
       Reader reader3 = new Reader();
       reader3.setName("王五");
     
       //注冊閱讀者
       subject.attach(reader1);
       subject.attach(reader2);
       subject.attach(reader3);
     
       //要出報紙啦
       subject.setContent("本期內(nèi)容是觀察者模式");
    }
}

運行結(jié)果如下:

張三收到報紙了,閱讀先。內(nèi)容是===本期內(nèi)容是觀察者模式
李四收到報紙了,閱讀先。內(nèi)容是===本期內(nèi)容是觀察者模式
王五收到報紙了,閱讀先。內(nèi)容是===本期內(nèi)容是觀察者模式

你還可以通過改變注冊的觀察者,或者是注冊了又退訂,來看看輸出的結(jié)果。會發(fā)現(xiàn)沒有注冊或者退訂的觀察者是收不到報紙的。

如同前面的示例,讀者和報社是一種典型的一對多的關(guān)系,一個報社有多個讀者,當(dāng)報社的狀態(tài)發(fā)生改變,也就是出版新報紙的時候,所有注冊的讀者都會得到通知,然后讀者會拿到報紙,讀者會去閱讀報紙并進(jìn)行后續(xù)的操作。

3 模式講解#

3.1 認(rèn)識觀察者模式##

  1. 目標(biāo)和觀察者之間的關(guān)系

按照模式的定義,目標(biāo)和觀察者之間是典型的一對多的關(guān)系。

但是要注意,如果觀察者只有一個,也是可以的,這樣就變相實現(xiàn)了目標(biāo)和觀察者之間一對一的關(guān)系,這也使得在處理一個對象的狀態(tài)變化會影響到另一個對象的時候,也可以考慮使用觀察者模式。

同樣的,一個觀察者也可以觀察多個目標(biāo),如果觀察者為多個目標(biāo)定義的通知更新方法都是update方法的話,這會帶來麻煩,因為需要接收多個目標(biāo)的通知,如果是一個update的方法,那就需要在方法內(nèi)部區(qū)分,到底這個更新的通知來自于哪一個目標(biāo),不同的目標(biāo)有不同的后續(xù)操作

一般情況下,觀察者應(yīng)該為不同的觀察者目標(biāo),定義不同的回調(diào)方法,這樣實現(xiàn)最簡單,不需要在update方法內(nèi)部進(jìn)行區(qū)分。

  1. 單向依賴

在觀察者模式中,觀察者和目標(biāo)是單向依賴的,只有觀察者依賴于目標(biāo),而目標(biāo)是不會依賴于觀察者的

它們之間聯(lián)系的主動權(quán)掌握在目標(biāo)手中,只有目標(biāo)知道什么時候需要通知觀察者,在整個過程中,觀察者始終是被動的,被動的等待目標(biāo)的通知,等待目標(biāo)傳值給它。

對目標(biāo)而言,所有的觀察者都是一樣的,目標(biāo)會一視同仁的對待。當(dāng)然也可以通過在目標(biāo)里面進(jìn)行控制,實現(xiàn)有區(qū)別對待觀察者,比如某些狀態(tài)變化,只需要通知部分觀察者,但那是屬于稍微變形的用法了,不屬于標(biāo)準(zhǔn)的、原始的觀察者模式了。

  1. 基本的實現(xiàn)說明

具體的目標(biāo)實現(xiàn)對象要能維護(hù)觀察者的注冊信息,最簡單的實現(xiàn)方案就如同前面的例子那樣,采用一個集合來保存觀察者的注冊信息。

具體的目標(biāo)實現(xiàn)對象需要維護(hù)引起通知的狀態(tài),一般情況下是目標(biāo)自身的狀態(tài),變形使用的情況下,也可以是別的對象的狀態(tài)。

具體的觀察者實現(xiàn)對象需要能接收目標(biāo)的通知,能夠接收目標(biāo)傳遞的數(shù)據(jù),或者是能夠主動去獲取目標(biāo)的數(shù)據(jù),并進(jìn)行后續(xù)處理。

如果是一個觀察者觀察多個目標(biāo),那么在觀察者的更新方法里面,需要去判斷是來自哪一個目標(biāo)的通知。一種簡單的解決方案就是擴(kuò)展update方法,比如在方法里面多傳遞一個參數(shù)進(jìn)行區(qū)分等;還有一種更簡單的方法,那就是干脆定義不同的回調(diào)方法。

  1. 命名建議

觀察者模式又被稱為發(fā)布-訂閱模式;

目標(biāo)接口的定義,建議在名稱后面跟Subject;

觀察者接口的定義,建議在名稱后面跟Observer;

觀察者接口的更新方法,建議名稱為update,當(dāng)然方法的參數(shù)可以根據(jù)需要定義,參數(shù)個數(shù)不限、參數(shù)類型不限;

  1. 觸發(fā)通知的時機(jī)

在實現(xiàn)觀察者模式的時候,一定要注意觸發(fā)通知的時機(jī),一般情況下,是在完成了狀態(tài)維護(hù)后觸發(fā),因為通知會傳遞數(shù)據(jù),不能夠先通知后改數(shù)據(jù),這很容易出問題,會導(dǎo)致觀察者和目標(biāo)對象的狀態(tài)不一致。比如:目標(biāo)一發(fā)出通知,就有觀察者來取值,結(jié)果目標(biāo)還沒有更新數(shù)據(jù),這就明顯造成了錯誤。如下示例就是有問題的了,示例代碼如下:

public void setContent(String content) {
    //一激動,目標(biāo)先發(fā)出通知了,然后才修改自己的數(shù)據(jù),這會造成問題
    notifyAllReader();  
    this.content = content;
}
  1. 相互觀察

在某些應(yīng)用里面,可能會出現(xiàn)目標(biāo)和觀察者相互觀察的情況。什么意思呢,比如有兩套觀察者模式的應(yīng)用,其中一套觀察者模式的實現(xiàn)是A對象、B對象觀察C對象;在另一套觀察者模式的實現(xiàn)里面,實現(xiàn)的是B對象、C對象觀察A對象,那么A對象和C對象就是在相互觀察。

換句話說,A對象的狀態(tài)變化會引起C對象的聯(lián)動操作,反過來,C 對象的狀態(tài)變化也會引起A對象的聯(lián)動操作。對于出現(xiàn)這種狀況,要特別小心處理,因為可能會出現(xiàn)死循環(huán)的情況。

  1. 觀察者模式的調(diào)用順序示意圖

在使用觀察者模式時,會很明顯的分成兩個階段,第一個階段是準(zhǔn)備階段,也就是維護(hù)目標(biāo)和觀察者關(guān)系的階段,這個階段的調(diào)用順序如圖所示:

觀察者模式準(zhǔn)備階段示意圖

接下來就是實際的運行階段了,這個階段的調(diào)用順序如圖所示:

觀察者模式運行階段示意圖
  1. 通知的順序

從理論上說,當(dāng)目標(biāo)對象的狀態(tài)變化后通知所有觀察者的時候,順序是不確定的,因此觀察者實現(xiàn)的功能,絕對不要依賴于通知的順序,也就是說,多個觀察者之間的功能是平行的,相互不應(yīng)該有先后的依賴關(guān)系

3.2 推模型和拉模型##

在觀察者模式的實現(xiàn)里面,又分為推模型和拉模型兩種方式,什么意思呢?

  1. 推模型

目標(biāo)對象主動向觀察者推送目標(biāo)的詳細(xì)信息,不管觀察者是否需要,推送的信息通常是目標(biāo)對象的全部或部分?jǐn)?shù)據(jù),相當(dāng)于是在廣播通信。

  1. 拉模型

目標(biāo)對象在通知觀察者的時候,只傳遞少量信息,如果觀察者需要更具體的信息,由觀察者主動到目標(biāo)對象中獲取,相當(dāng)于是觀察者從目標(biāo)對象中拉數(shù)據(jù)。

一般這種模型的實現(xiàn)中,會把目標(biāo)對象自身通過update方法傳遞給觀察者,這樣在觀察者需要獲取數(shù)據(jù)的時候,就可以通過這個引用來獲取了。

根據(jù)上面的描述,發(fā)現(xiàn)前面的例子就是典型的拉模型,那么推模型如何實現(xiàn)呢,還是來看個示例吧,這樣會比較清楚。

  1. 推模型的觀察者接口

根據(jù)前面的講述,推模型通常都是把需要傳遞的數(shù)據(jù)直接推送給觀察者對象,所以觀察者接口中的update方法的參數(shù)需要發(fā)生變化,示例代碼如下:

/**
 * 觀察者,比如報紙的讀者
 */
public interface Observer {
    /**
     * 被通知的方法,直接把報紙的內(nèi)容推送過來
     * @param content 報紙的內(nèi)容
     */
    public void update(String content);
}
  1. 推模型的觀察者的具體實現(xiàn)

以前需要到目標(biāo)對象里面獲取自己需要的數(shù)據(jù),現(xiàn)在是直接接收傳入的數(shù)據(jù),這就是改變的地方,示例代碼如下:

public class Reader implements Observer{
    /**
     * 讀者的姓名
     */
    private String name;

    public void update(String content) {
       //這是采用推的方式
       System.out.println(name+"收到報紙了,閱讀先。內(nèi)容是==="+content);
    }
    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
}
  1. 推模型的目標(biāo)對象

跟拉模型的目標(biāo)實現(xiàn)相比,有一些變化:

一個就是通知所有觀察者的方法,以前是沒有參數(shù)的,現(xiàn)在需要傳入需要主動推送的數(shù)據(jù);

另外一個就是在循環(huán)通知觀察者的時候,也就是循環(huán)調(diào)用觀察者的update方法的時候,傳入的參數(shù)不同了;

示例代碼如下:

/**
 * 目標(biāo)對象,作為被觀察者,使用推模型
 */
public class Subject {
    /**
     * 用來保存注冊的觀察者對象,也就是報紙的訂閱者
     */
    private List<Observer> readers = new ArrayList<Observer>();
    /**
     * 報紙的讀者需要先向報社訂閱,先要注冊
     * @param reader 報紙的讀者
     * @return 是否注冊成功
     */
    public void attach(Observer reader) {
       readers.add(reader);
    }
    /**
     * 報紙的讀者可以取消訂閱
     * @param reader 報紙的讀者
     * @return 是否取消成功
     */
    public void detach(Observer reader) {
       readers.remove(reader);
    }
    /**
     * 當(dāng)每期報紙印刷出來后,就要迅速的主動的被送到讀者的手中,
     * 相當(dāng)于通知讀者,讓他們知道
     * @param content 要主動推送的內(nèi)容
     */
    protected void notifyObservers(String content) {
       for(Observer reader : readers){
           reader.update(content);
       }
    }
}
  1. 推模型的目標(biāo)具體實現(xiàn)

跟拉模型相比,有一點變化,就是在調(diào)用通知觀察者的方法的時候,需要傳入?yún)?shù)了,拉模型的實現(xiàn)中是不需要的,示例代碼如下:

public class NewsPaper extends Subject{
    private String content;
    public String getContent() {
       return content;
    }
    public void setContent(String content) {
       this.content = content;
       //內(nèi)容有了,說明又出報紙了,那就通知所有的讀者
       notifyObservers(content);
    }
}
  1. 推模型的客戶端使用

跟拉模型一樣,沒有變化。

  1. 關(guān)于兩種模型的比較

兩種實現(xiàn)模型,在開發(fā)的時候,究竟應(yīng)該使用哪一種,還是應(yīng)該具體問題具體分析。這里,只是把兩種模型進(jìn)行一個簡單的比較。

推模型是假定目標(biāo)對象知道觀察者需要的數(shù)據(jù);而拉模型是目標(biāo)對象不知道觀察者具體需要什么數(shù)據(jù),沒有辦法的情況下,干脆把自身傳給觀察者,讓觀察者自己去按需取值。

推模型可能會使得觀察者對象難以復(fù)用,因為觀察者定義的update方法是按需而定義的,可能無法兼顧沒有考慮到的使用情況。這就意味著出現(xiàn)新情況的時候,就可能需要提供新的update方法,或者是干脆重新實現(xiàn)觀察者。

而拉模型就不會造成這樣的情況,因為拉模型下,update方法的參數(shù)是目標(biāo)對象本身,這基本上是目標(biāo)對象能傳遞的最大數(shù)據(jù)集合了,基本上可以適應(yīng)各種情況的需要。

3.3 Java中的觀察者模式##

估計有些朋友在看前面的內(nèi)容的時候,心里就嘀咕上了,Java里面不是已經(jīng)有了觀察者模式的部分實現(xiàn)嗎,為何還要全部自己從頭做呢?

主要是為了讓大家更好的理解觀察者模式本身,而不用受Java語言實現(xiàn)的限制。

好了,下面就來看看如何利用Java中已有的功能來實現(xiàn)觀察者模式。在java.util包里面有一個類Observable,它實現(xiàn)了大部分我們需要的目標(biāo)的功能還有一個接口Observer,它里面定義了update的方法,就是觀察者的接口

因此,利用Java中已有的功能來實現(xiàn)觀察者模式非常簡單,跟前面完全由自己來實現(xiàn)觀察者模式相比有如下改變:

不需要再定義觀察者和目標(biāo)的接口了,JDK幫忙定義了;

具體的目標(biāo)實現(xiàn)里面不需要再維護(hù)觀察者的注冊信息了,這個在Java中的Observable類里面,已經(jīng)幫忙實現(xiàn)好了;

觸發(fā)通知的方式有一點變化,要先調(diào)用setChanged方法,這個是Java為了幫助實現(xiàn)更精確的觸發(fā)控制而提供的功能;

具體觀察者的實現(xiàn)里面,update方法其實能同時支持推模型和拉模型,這個是Java在定義的時候,就已經(jīng)考慮進(jìn)去了;

好了,說了這么多,還是看看例子會比較直觀。

  1. 新的目標(biāo)的實現(xiàn),不再需要自己來實現(xiàn)Subject定義,在具體實現(xiàn)的時候,也不是繼承Subject了,而是改成繼承Java中定義的Observable,示例代碼如下:
/**
 * 報紙對象,具體的目標(biāo)實現(xiàn)
 */
public class NewsPaper extends  java.util.Observable   {
    /**
     * 報紙的具體內(nèi)容
     */
    private String content;
    /**
     * 獲取報紙的具體內(nèi)容
     * @return 報紙的具體內(nèi)容
     */
    public String getContent() {
       return content;
    }
    /**
     * 示意,設(shè)置報紙的具體內(nèi)容,相當(dāng)于要出版報紙了
     * @param content 報紙的具體內(nèi)容
     */
    public void setContent(String content) {
       this.content = content;
       //內(nèi)容有了,說明又出新報紙了,那就通知所有的讀者
       //注意在用Java中的Observer模式的時候,下面這句話不可少
       this.setChanged();
       //然后主動通知,這里用的是推的方式
       this.notifyObservers(this.content);
       //如果用拉的方式,這么調(diào)用
       //this.notifyObservers();
    }
}
  1. 再看看新的觀察者的實現(xiàn),不是實現(xiàn)自己定義的觀察者接口,而是實現(xiàn)由Java提供的Observer接口,示例代碼如下:
/**
 * 真正的讀者,為了簡單就描述一下姓名
 */
public class Reader implements   java.util.Observer   {
    /**
     * 讀者的姓名
     */
    private String name;
    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
    public void update(Observable o, Object obj) {
       //這是采用推的方式
       System.out.println(name+"收到報紙了,閱讀先。目標(biāo)推過來的內(nèi)容是==="+obj);

       //這是獲取拉的數(shù)據(jù)
       System.out.println(name+"收到報紙了,閱讀先。主動到目標(biāo)對象去拉的內(nèi)容是==="+((NewsPaper)o).getContent());
    }
}
  1. 客戶端使用

客戶端跟前面的寫法沒有太大改變,主要在注冊閱讀者的時候,調(diào)用的方法跟以前不一樣了,示例代碼如下:

public class Client {
    public static void main(String[] args) {
       //創(chuàng)建一個報紙,作為被觀察者
       NewsPaper subject = new NewsPaper();
       //創(chuàng)建閱讀者,也就是觀察者
       Reader reader1 = new Reader();
       reader1.setName("張三");
     
       Reader reader2 = new Reader();
       reader2.setName("李四");
     
       Reader reader3 = new Reader();
       reader3.setName("王五");
     
       //注冊閱讀者
       subject.addObserver(reader1);
       subject.addObserver(reader2);
       subject.addObserver(reader3);
     
       //要出報紙啦
       subject.setContent("本期內(nèi)容是觀察者模式");
    }
}

趕緊測試一下,運行運行,看看結(jié)果,運行結(jié)果如下所示:

王五收到報紙了,閱讀先。目標(biāo)推過來的內(nèi)容是===本期內(nèi)容是觀察者模式
王五收到報紙了,閱讀先。主動到目標(biāo)對象去拉的內(nèi)容是===本期內(nèi)容是觀察者模式
李四收到報紙了,閱讀先。目標(biāo)推過來的內(nèi)容是===本期內(nèi)容是觀察者模式
李四收到報紙了,閱讀先。主動到目標(biāo)對象去拉的內(nèi)容是===本期內(nèi)容是觀察者模式
張三收到報紙了,閱讀先。目標(biāo)推過來的內(nèi)容是===本期內(nèi)容是觀察者模式
張三收到報紙了,閱讀先。主動到目標(biāo)對象去拉的內(nèi)容是===本期內(nèi)容是觀察者模式

然后好好對比自己實現(xiàn)觀察者模式和使用Java已有的功能來實現(xiàn)觀察者模式,看看有什么不同,有什么相同,好好體會一下。

3.4 觀察者模式的優(yōu)缺點##

  1. 觀察者模式實現(xiàn)了觀察者和目標(biāo)之間的抽象耦合

原本目標(biāo)對象在狀態(tài)發(fā)生改變的時候,需要直接調(diào)用所有的觀察者對象,但是抽象出觀察者接口過后,目標(biāo)和觀察者就只是在抽象層面上耦合了,也就是說目標(biāo)只是知道觀察者接口,并不知道具體的觀察者的類,從而實現(xiàn)目標(biāo)類和具體的觀察者類之間解耦。

  1. 觀察者模式實現(xiàn)了動態(tài)聯(lián)動

所謂聯(lián)動,就是做一個操作會引起其它相關(guān)的操作。由于觀察者模式對觀察者注冊實行管理,那就可以在運行期間,通過動態(tài)的控制注冊的觀察者,來控制某個動作的聯(lián)動范圍,從而實現(xiàn)動態(tài)聯(lián)動。

  1. 觀察者模式支持廣播通信

由于目標(biāo)發(fā)送通知給觀察者是面向所有注冊的觀察者,所以每次目標(biāo)通知的信息就要對所有注冊的觀察者進(jìn)行廣播。當(dāng)然,也可以通過在目標(biāo)上添加新的功能來限制廣播的范圍。

在廣播通信的時候要注意一個問題,就是相互廣播造成死循環(huán)的問題。比如A和B兩個對象互為觀察者和目標(biāo)對象,A對象發(fā)生狀態(tài)變化,然后A來廣播信息,B對象接收到通知后,在處理過程中,使得B對象的狀態(tài)也發(fā)生了改變,然后B來廣播信息,然后A對象接到通知后,又觸發(fā)廣播信息……,如此A引起B(yǎng)變化,B又引起A變化,從而一直相互廣播信息,就造成死循環(huán)了。

  1. 觀察者模式可能會引起無謂的操作

由于觀察者模式每次都是廣播通信,不管觀察者需不需要,每個觀察者都會被調(diào)用update方法,如果觀察者不需要執(zhí)行相應(yīng)處理,那么這次操作就浪費了。

其實浪費了還好,怕就怕引起了誤更新,那就麻煩了,比如:本應(yīng)該在執(zhí)行這次狀態(tài)更新前把某個觀察者刪除掉,這樣通知的時候就沒有這個觀察者了,但是現(xiàn)在忘掉了,那么就會引起誤操作。

3.5 思考觀察者模式##

  1. 觀察者模式的本質(zhì)

觀察者模式的本質(zhì):觸發(fā)聯(lián)動。

當(dāng)修改目標(biāo)對象的狀態(tài)的時候,就會觸發(fā)相應(yīng)的通知,然后會循環(huán)調(diào)用所有注冊的觀察者對象的相應(yīng)方法,其實就相當(dāng)于聯(lián)動調(diào)用這些觀察者的方法。

而且這個聯(lián)動還是動態(tài)的,可以通過注冊和取消注冊來控制觀察者,因而可以在程序運行期間,通過動態(tài)的控制觀察者,來變相的實現(xiàn)添加和刪除某些功能處理,這些功能就是觀察者在update的時候執(zhí)行的功能。

同時目標(biāo)對象和觀察者對象的解耦,又保證了無論觀察者發(fā)生怎樣的變化,目標(biāo)對象總是能夠正確地聯(lián)動過來。

理解這個本質(zhì)對我們非常有用,對于我們識別和使用觀察者模式有非常重要的意義,尤其是在變形使用的時候,萬變不離其宗。

  1. 何時選用觀察者模式

建議在如下情況中,選用觀察者模式:

當(dāng)一個抽象模型有兩個方面,其中一個方面的操作依賴于另一個方面的狀態(tài)變化,那么就可以選用觀察者模式,將這兩者封裝成觀察者和目標(biāo)對象,當(dāng)目標(biāo)對象變化的時候,依賴于它的觀察者對象也會發(fā)生相應(yīng)的變化。這樣就把抽象模型的這兩個方面分離開了,使得它們可以獨立的改變和復(fù)用。

如果在更改一個對象的時候,需要同時連帶改變其它的對象,而且不知道究竟應(yīng)該有多少對象需要被連帶改變,這種情況可以選用觀察者模式,被更改的那一個對象很明顯就相當(dāng)于是目標(biāo)對象,而需要連帶修改的多個其它對象,就作為多個觀察者對象了。

當(dāng)一個對象必須通知其它的對象,但是你又希望這個對象和其它被它通知的對象是松散耦合的,也就是說這個對象其實不想知道具體被通知的對象,這種情況可以選用觀察者模式,這個對象就相當(dāng)于是目標(biāo)對象,而被它通知的對象就是觀察者對象了。

3.6 Swing中的觀察者模式##

Java的Swing中到處都是觀察者模式的身影,比如大家熟悉的事件處理,就是典型的觀察者模式的應(yīng)用。(說明一下:早期的Swing事件處理用的是職責(zé)鏈)

Swing組件是被觀察的目標(biāo),而每個實現(xiàn)監(jiān)聽器的類就是觀察者,監(jiān)聽器的接口就是觀察者的接口,在調(diào)用addXXXListener方法的時候就相當(dāng)于注冊觀察者。當(dāng)組件被點擊,狀態(tài)發(fā)生改變的時候,就會產(chǎn)生相應(yīng)的通知,會調(diào)用注冊的觀察者的方法,就是我們所實現(xiàn)的監(jiān)聽器的方法。

從這里還可以學(xué)一招:如何處理一個觀察者觀察多個目標(biāo)對象?

你看一個Swing的應(yīng)用程序,作為一個觀察者,經(jīng)常會注冊觀察多個不同的目標(biāo)對象,也就是同一類,既實現(xiàn)了按鈕組件的事件處理,又實現(xiàn)了文本框組件的事件處理,是怎么做到的呢?

答案就在監(jiān)聽器接口上,這些監(jiān)聽器接口就相當(dāng)于觀察者接口,也就是說一個觀察者要觀察多個目標(biāo)對象,只要不同的目標(biāo)對象使用不同的觀察者接口就好了,當(dāng)然,這些接口里面的方法也不相同,不再都是update方法了。這樣一來,不同的目標(biāo)對象通知觀察者所調(diào)用的方法也就不同了,這樣在具體實現(xiàn)觀察者的時候,也就實現(xiàn)成不同的方法,自然就區(qū)分開了。

3.7 簡單變形示例——區(qū)別對待觀察者##

首先聲明,這里只是舉一個非常簡單的變形使用的例子,也可算是基本的觀察者模式的功能加強(qiáng),事實上可以有很多很多的變形應(yīng)用,這也是為什么我們特別強(qiáng)調(diào)大家要深入理解每個設(shè)計模式,要把握每個模式的本質(zhì)的原因了。

  1. 范例需求

這是一個實際系統(tǒng)的簡化需求:在一個水質(zhì)監(jiān)測系統(tǒng)中有這樣一個功能,當(dāng)水中的雜質(zhì)為正常的時候,只是通知監(jiān)測人員做記錄;當(dāng)為輕度污染的時候,除了通知監(jiān)測人員做記錄外,還要通知預(yù)警人員,判斷是否需要預(yù)警;當(dāng)為中度或者高度污染的時候,除了通知監(jiān)測人員做記錄外,還要通知預(yù)警人員,判斷是否需要預(yù)警,同時還要通知監(jiān)測部門領(lǐng)導(dǎo)做相應(yīng)的處理。

  1. 解決思路和范例代碼

分析上述需求就會發(fā)現(xiàn),對于水質(zhì)污染這件事情,有可能會涉及到監(jiān)測員、預(yù)警人員、監(jiān)測部門領(lǐng)導(dǎo),根據(jù)不同的水質(zhì)污染情況涉及到不同的人員,也就是說,監(jiān)測員、預(yù)警人員、監(jiān)測部門領(lǐng)導(dǎo)他們?nèi)呤瞧叫械模氊?zé)都是處理水質(zhì)污染,但是處理的范圍不一樣。

因此很容易套用上觀察者模式,如果把水質(zhì)污染的記錄當(dāng)作被觀察的目標(biāo)的話,那么監(jiān)測員、預(yù)警人員和監(jiān)測部門領(lǐng)導(dǎo)就都是觀察者了。

前面學(xué)過的觀察者模式,當(dāng)目標(biāo)通知觀察者的時候是全部都通知,但是現(xiàn)在這個需求是不同的情況來讓不同的人處理,怎么辦呢?

解決的方式通常有兩種,一種是目標(biāo)可以通知,但是觀察者不做任何操作;另外一種是在目標(biāo)里面進(jìn)行判斷,干脆就不通知了。兩種實現(xiàn)方式各有千秋,這里選擇后面一種方式來示例,這種方式能夠統(tǒng)一邏輯控制,并進(jìn)行觀察者的統(tǒng)一分派,有利于業(yè)務(wù)控制和今后的擴(kuò)展。

(1)先來定義觀察者的接口,這個接口跟前面的示例差別也不大,只是新加了訪問觀察人員職務(wù)的方法,示例代碼如下:

/**
 * 水質(zhì)觀察者接口定義
 */
public interface WatcherObserver {
    /**
     * 被通知的方法
     * @param subject 傳入被觀察的目標(biāo)對象
     */
    public void update(WaterQualitySubject subject);
    /**
     * 設(shè)置觀察人員的職務(wù)
     * @param job 觀察人員的職務(wù)
     */
    public void setJob(String job);
    /**
     * 獲取觀察人員的職務(wù)
     * @return 觀察人員的職務(wù)
     */
    public String getJob();
}

(2)定義完接口后,來看看觀察者的具體實現(xiàn),示例代碼如下:

/**
 * 具體的觀察者實現(xiàn)
 */
public class Watcher implements WatcherObserver{
    /**
     * 職務(wù)
     */
    private String job;
    public String getJob() {
       return this.job;
    }  
    public void setJob(String job) {
       this.job = job;
    }

    public void update(WaterQualitySubject subject) {
       //這里采用的是拉的方式
       System.out.println(job+"獲取到通知,當(dāng)前污染級別為:"+subject.getPolluteLevel());
    }
}

(3)接下來定義目標(biāo)的父對象,跟以前相比有些改變:

把父類實現(xiàn)成抽象的,因為在里面要定義抽象的方法;

原來通知所有的觀察者的方法被去掉了,這個方法現(xiàn)在需要由子類去實現(xiàn),要按照業(yè)務(wù)來有區(qū)別的來對待觀察者,得看看是否需要通知觀察者;

新添加一個水質(zhì)污染級別的業(yè)務(wù)方法,這樣在觀察者獲取目標(biāo)對象的數(shù)據(jù)的時候,就不需要再知道具體的目標(biāo)對象,也不需要強(qiáng)制造型了;

/**
 * 定義水質(zhì)監(jiān)測的目標(biāo)對象
 */
public abstract class WaterQualitySubject {
    /**
     * 用來保存注冊的觀察者對象
     */
    protected List<WatcherObserver> observers = new ArrayList<WatcherObserver>();
    /**
     * 注冊觀察者對象
     * @param observer 觀察者對象
     */
    public void attach(WatcherObserver observer) {
       observers.add(observer);
    }
    /**
     * 刪除觀察者對象
     * @param observer 觀察者對象
     */
    public void detach(WatcherObserver observer) {
       observers.remove(observer);
    }
    /**
     * 通知相應(yīng)的觀察者對象
     */
    public abstract void notifyWatchers();
    /**
     * 獲取水質(zhì)污染的級別
     * @return 水質(zhì)污染的級別
     */
    public abstract int getPolluteLevel();
}

(4)接下來重點看看目標(biāo)的實現(xiàn),在目標(biāo)對象里面,添加一個描述污染級別的屬性,在判斷是否需要通知觀察者的時候,不同的污染程度對應(yīng)會通知不同的觀察者,示例代碼如下:

/**
 * 具體的水質(zhì)監(jiān)測對象
 */
public class WaterQuality extends WaterQualitySubject{
    /**
     * 污染的級別,0表示正常,1表示輕度污染,2表示中度污染,3表示高度污染
     */
    private int polluteLevel = 0;
    /**
     * 獲取水質(zhì)污染的級別
     * @return 水質(zhì)污染的級別
     */
    public int getPolluteLevel() {
       return polluteLevel;
    }
    /**
     * 當(dāng)監(jiān)測水質(zhì)情況后,設(shè)置水質(zhì)污染的級別
     * @param polluteLevel 水質(zhì)污染的級別
     */
    public void setPolluteLevel(int polluteLevel) {
       this.polluteLevel = polluteLevel;
       //通知相應(yīng)的觀察者
       this.notifyWatchers();
    }
    /**
     * 通知相應(yīng)的觀察者對象
     */
    public void notifyWatchers() {
       //循環(huán)所有注冊的觀察者
       for(WatcherObserver watcher : observers){
           //開始根據(jù)污染級別判斷是否需要通知,由這里總控
           if(this.polluteLevel >= 0){
               //通知監(jiān)測員做記錄
               if("監(jiān)測人員".equals(watcher.getJob())){
                   watcher.update(this);
               }
           }
           if(this.polluteLevel >= 1){
               //通知預(yù)警人員
               if("預(yù)警人員".equals(watcher.getJob())){
                   watcher.update(this);
               }
           }
           if(this.polluteLevel >= 2){
               //通知監(jiān)測部門領(lǐng)導(dǎo)
               if("監(jiān)測部門領(lǐng)導(dǎo)".equals(watcher.getJob())){
                   watcher.update(this);
               }
           }
       }
    }
}

(5)大功告成,來寫個客戶端,測試一下,示例代碼如下:

public class Client {
    public static void main(String[] args) {
       //創(chuàng)建水質(zhì)主題對象
       WaterQuality subject = new WaterQuality();
       //創(chuàng)建幾個觀察者
       WatcherObserver watcher1 = new Watcher();
       watcher1.setJob("監(jiān)測人員");
       WatcherObserver watcher2 = new Watcher();
       watcher2.setJob("預(yù)警人員");
       WatcherObserver watcher3 = new Watcher();
       watcher3.setJob("監(jiān)測部門領(lǐng)導(dǎo)");

       //注冊觀察者
       subject.attach(watcher1);
       subject.attach(watcher2);
       subject.attach(watcher3);
     
       //填寫水質(zhì)報告
       System.out.println("當(dāng)水質(zhì)為正常的時候------------------〉");
       subject.setPolluteLevel(0);
       System.out.println("當(dāng)水質(zhì)為輕度污染的時候---------------〉");
       subject.setPolluteLevel(1);
       System.out.println("當(dāng)水質(zhì)為中度污染的時候---------------〉");
       subject.setPolluteLevel(2);
    }
}

(6)運行一下,看看結(jié)果,如下:

當(dāng)水質(zhì)為正常的時候------------------〉
監(jiān)測人員獲取到通知,當(dāng)前污染級別為:0
當(dāng)水質(zhì)為輕度污染的時候---------------〉
監(jiān)測人員獲取到通知,當(dāng)前污染級別為:1
預(yù)警人員獲取到通知,當(dāng)前污染級別為:1
當(dāng)水質(zhì)為中度污染的時候---------------〉
監(jiān)測人員獲取到通知,當(dāng)前污染級別為:2
預(yù)警人員獲取到通知,當(dāng)前污染級別為:2
監(jiān)測部門領(lǐng)導(dǎo)獲取到通知,當(dāng)前污染級別為:2

仔細(xì)觀察上面輸出的結(jié)果,你會發(fā)現(xiàn),當(dāng)填寫不同的污染級別時,被通知的人員是不同的。但是這些觀察者是不知道這些不同的,觀察者只是在自己獲得通知的時候去執(zhí)行自己的工作。具體要不要通知,什么時候通知都是目標(biāo)對象的工作

3.8 相關(guān)模式##

  1. 觀察者模式和狀態(tài)模式

觀察者模式和狀態(tài)模式是有相似之處的。

觀察者模式是當(dāng)目標(biāo)狀態(tài)發(fā)生改變時,觸發(fā)并通知觀察者,讓觀察者去執(zhí)行相應(yīng)的操作。而狀態(tài)模式是根據(jù)不同的狀態(tài),選擇不同的實現(xiàn),這個實現(xiàn)類的主要功能就是針對狀態(tài)的相應(yīng)的操作,它不像觀察者,觀察者本身還有很多其它的功能,接收通知并執(zhí)行相應(yīng)處理只是觀察者的部分功能。

當(dāng)然觀察者模式和狀態(tài)模式是可以結(jié)合使用的。觀察者模式的重心在觸發(fā)聯(lián)動,但是到底決定哪些觀察者會被聯(lián)動,這時就可以采用狀態(tài)模式來實現(xiàn)了,也可以采用策略模式來進(jìn)行選擇需要聯(lián)動的觀察者

  1. 觀察者模式和中介者模式

觀察者模式和中介者模式是可以結(jié)合使用的。

前面的例子中目標(biāo)都只是簡單的通知一下,然后讓各個觀察者自己去完成更新就結(jié)束了。如果觀察者和被觀察的目標(biāo)之間的交互關(guān)系很復(fù)雜,比如:有一個界面,里面有三個下拉列表組件,分別是選擇國家、省份/州、具體的城市,很明顯這是一個三級聯(lián)動,當(dāng)你選擇一個國家的時候,省份/州應(yīng)該相應(yīng)改變數(shù)據(jù),省份/州一改變,具體的城市也需要改變。

這種情況下,很明顯需要相關(guān)的狀態(tài)都聯(lián)動準(zhǔn)備好了,然后再一次性的通知觀察者,就是界面做更新處理,不會國家改變一下,省份和城市還沒有改,就通知界面更新。這種情況就可以使用中介者模式來封裝觀察者和目標(biāo)的關(guān)系。

在使用Swing的小型應(yīng)用里面,也可以使用中介者模式。比如:把一個界面所有的事件用一個對象來處理,把一個組件觸發(fā)事件過后,需要操作其它組件的動作都封裝到一起,這個對象就是典型的中介者。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內(nèi)容

  • 1.初識觀察者模式 定義對象間的一種一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被...
    王偵閱讀 880評論 0 1
  • 【學(xué)習(xí)難度:★★★☆☆,使用頻率:★★★★★】直接出處:觀察者模式梳理和學(xué)習(xí):https://github.com...
    BruceOuyang閱讀 1,550評論 1 5
  • 1 意圖 定義對象間的一種一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。...
    10xjzheng閱讀 971評論 0 0
  • 觀察者設(shè)計模式 一、概念 定義對象之間的一種一對多的依賴關(guān)系,使得每當(dāng)一個對象狀態(tài)發(fā)生改變時,其相關(guān)依賴對象皆得到...
    我可能是個假開發(fā)閱讀 1,083評論 1 4
  • 本文的結(jié)構(gòu)如下: 什么是觀察者模式 為什么要用該模式 模式的結(jié)構(gòu) 代碼示例 推模型和拉模型 優(yōu)點和缺點 適用環(huán)境 ...
    w1992wishes閱讀 1,446評論 0 16