觀察者模式(Observer)

定義

觀察者模式是對(duì)象的行為模式粪薛,又叫發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式盗舰、源-監(jiān)聽(tīng)器(Source/Listener)模式或從屬者(Dependents)模式蕊程【肿埃—閻宏博士《JAVA與模式》


觀察者模式的結(jié)構(gòu)

角色
1晤锹、抽象主題(Subject)角色:抽象主題角色把所有對(duì)觀察者對(duì)象的引用保存在一個(gè)聚集(比如ArrayList對(duì)象)里摩幔,每個(gè)主題都可以有任何數(shù)量的觀察者。抽象主題提供一個(gè)接口鞭铆,可以增加和刪除觀察者對(duì)象或衡,抽象主題角色又叫做抽象被觀察者(Observable)角色。

2车遂、具體主題(ConcreteSubject)角色:將有關(guān)狀態(tài)存入具體觀察者對(duì)象封断;在具體主題的內(nèi)部狀態(tài)改變時(shí),給所有登記過(guò)的觀察者發(fā)出通知舶担。具體主題角色又叫做具體被觀察者(Concrete Observable)角色坡疼。

3、抽象觀察者(Observer)角色:為所有的具體觀察者定義一個(gè)接口衣陶,在得到主題的通知時(shí)更新自己柄瑰,這個(gè)接口叫做更新接口废岂。

4、具體觀察者(ConcreteObserver)角色:存儲(chǔ)與主題的狀態(tài)自恰的狀態(tài)狱意。具體觀察者角色實(shí)現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài) 像協(xié)調(diào)拯欧。如果需要详囤,具體觀察者角色可以保持一個(gè)指向具體主題對(duì)象的引用。

UML圖

觀察者模式UML圖.png

代碼

public abstract class Subject {
    protected List<Observer> observers = new ArrayList<Observer>();

    /**
     * 注冊(cè)觀察者對(duì)象
     * @param observer    觀察者對(duì)象
     */
    public void attach(Observer observer){
        observers.add(observer);
    }
    /**
     * 刪除觀察者對(duì)象
     * @param observer    觀察者對(duì)象
     */
    public void detach(Observer observer){
        observers.remove(observer);
    }
    /**
     * 通知所有注冊(cè)的觀察者對(duì)象
     */
    public void nodifyObservers(String newState){
        for(Observer observer : observers){
            observer.update(newState);
        }
    }
}
public class Subject1 extends Subject{
    private String status;

    public void change(String newState){
        status = newState;
        System.out.println("主題狀態(tài)為:" + status);
        //狀態(tài)發(fā)生改變镐作,通知各個(gè)觀察者
        this.nodifyObservers(status);
    }

    public String getStatus() {
        return status;
    }
}
public interface Observer {
    public void update(String newStatus);
}
public class ObserverA implements Observer{

    private String status;

    @Override
    public void update(String newStatus) {
        System.out.println("ObserverA update status");
        this.status = newStatus;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}
public class ObserverB implements Observer{

    private String status;

    @Override
    public void update(String newStatus) {
        System.out.println("ObserverB update status");
        this.status = newStatus;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
}

客戶(hù)端類(lèi)

public class Client {
    public static void main(String[] args) {
        //創(chuàng)建主題對(duì)象
        Subject1 subject1 = new Subject1();

        //創(chuàng)建觀察者對(duì)象
        Observer observerA = new ObserverA();
        Observer observerB = new ObserverB();

        //將觀察者對(duì)象登記到主題對(duì)象上
        subject1.attach(observerA);
        subject1.attach(observerB);

        //改變主題對(duì)象的狀態(tài)
        subject1.change("new state");
    }
}

觀察者模式的兩種模型(推模型和拉模型)

推模型
主題對(duì)象向觀察者推送主題的詳細(xì)信息藏姐,不管觀察者是否需要,推送的信息通常是主題對(duì)象的全部或部分?jǐn)?shù)據(jù)该贾。

拉模型
主題對(duì)象在通知觀察者的時(shí)候羔杨,只傳遞少量信息。如果觀察者需要更具體的信息杨蛋,由觀察者主動(dòng)到主題對(duì)象中獲取兜材,相當(dāng)于是觀察者從主題對(duì)象中拉數(shù)據(jù)。一般這種模型的實(shí)現(xiàn)中逞力,會(huì)把主題對(duì)象自身通過(guò)update()方法傳遞給觀察者曙寡,這樣在觀察者需要獲取數(shù)據(jù)的時(shí)候,就可以通過(guò)這個(gè)引用來(lái)獲取了寇荧。

顯然举庶,前面的例子是典型的推模型,下面給一個(gè)拉模型的實(shí)例揩抡。拉模型通常都是把主題對(duì)象當(dāng)做參數(shù)傳遞户侥。

代碼

抽象觀察者

public interface Observer {
    public void update(Subject subject);
}

拉模型的具體觀察者類(lèi)

public class ObserverA implements Observer {
    //觀察者的狀態(tài)
    private String observerStatus;
    
    @Override
    public void update(Subject subject) {
        // 更新觀察者的狀態(tài),使其與目標(biāo)的狀態(tài)保持一致
        observerStatus = ((ObserverA)subject).getStatus();
        System.out.println("觀察者狀態(tài)為:"+observerStatus);
    }
}

拉模型的抽象主題類(lèi)

public abstract class Subject {
    protected List<Observer> observers = new ArrayList<Observer>();

    /**
     * 注冊(cè)觀察者對(duì)象
     * @param observer    觀察者對(duì)象
     */
    public void attach(Observer observer){
        observers.add(observer);
    }
    /**
     * 刪除觀察者對(duì)象
     * @param observer    觀察者對(duì)象
     */
    public void detach(Observer observer){
        observers.remove(observer);
    }
    /**
     * 通知所有注冊(cè)的觀察者對(duì)象
     */
    public void nodifyObservers(){
        for(Observer observer : observers){
            observer.update(this);
        }
    }
}

拉模型的具體主題類(lèi)

public class Subject1 extends Subject{
    private String status;

    public void change(String newState){
        status = newState;
        System.out.println("主題狀態(tài)為:" + status);
        this.nodifyObservers();
    }

    public String getStatus() {
        return status;
    }
}

兩種模型的對(duì)比
1峦嗤、推模型是假定主題對(duì)象知道觀察者需要的數(shù)據(jù)蕊唐;而拉模型是主題對(duì)象不知道觀察者具體需要什么數(shù)據(jù),沒(méi)有辦法的情況下烁设,干脆把自身傳遞給觀察者刃泌,讓觀察者自己去按需要取值。

2署尤、推模型可能會(huì)使得觀察者對(duì)象難以復(fù)用耙替,因?yàn)橛^察者的update()方法是按需要定義的參數(shù),可能無(wú)法兼顧沒(méi)有考慮到的使用情況曹体。這就意味著出現(xiàn)新情況的時(shí)候俗扇,就可能提供新的update()方法,或者是干脆重新實(shí)現(xiàn)觀察者箕别;而拉模型就不會(huì)造成這樣的情況铜幽,因?yàn)槔P拖轮托唬瑄pdate()方法的參數(shù)是主題對(duì)象本身,這基本上是主題對(duì)象能傳遞的最大數(shù)據(jù)集合了除抛,基本上可以適應(yīng)各種情況的需要狮杨。


Java提供的對(duì)觀察者模式的支持

Observer接口
這個(gè)接口只定義了一個(gè)方法,即update()方法到忽,當(dāng)被觀察者對(duì)象的狀態(tài)發(fā)生變化時(shí)橄教,被觀察者對(duì)象的notifyObservers()方法就會(huì)調(diào)用這一方法。

public interface Observer {
    void update(Observable o, Object arg);
}

Observable類(lèi)
被觀察者類(lèi)都是java.util.Observable類(lèi)的子類(lèi)喘漏。java.util.Observable提供公開(kāi)的方法支持觀察者對(duì)象护蝶,這些方法中有兩個(gè)對(duì)Observable的子類(lèi)非常重要:一個(gè)是setChanged(),另一個(gè)是notifyObservers()翩迈。第一方法setChanged()被調(diào)用之后會(huì)設(shè)置一個(gè)內(nèi)部標(biāo)記變量持灰,代表被觀察者對(duì)象的狀態(tài)發(fā)生了變化。第二個(gè)是notifyObservers()负饲,這個(gè)方法被調(diào)用時(shí)堤魁,會(huì)調(diào)用所有登記過(guò)的觀察者對(duì)象的update()方法,使這些觀察者對(duì)象可以更新自己返十。

public class Observable {
    private boolean changed = false;
    private Vector obs;
   
    /** Construct an Observable with zero Observers. */

    public Observable() {
        obs = new Vector();
    }

    /**
     * 將一個(gè)觀察者添加到觀察者聚集上面
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
    if (!obs.contains(o)) {
        obs.addElement(o);
    }
    }

    /**
     * 將一個(gè)觀察者從觀察者聚集上刪除
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
    notifyObservers(null);
    }

    /**
     * 如果本對(duì)象有變化(那時(shí)hasChanged 方法會(huì)返回true)
     * 調(diào)用本方法通知所有登記的觀察者姨涡,即調(diào)用它們的update()方法
     * 傳入this和arg作為參數(shù)
     */
    public void notifyObservers(Object arg) {
        Object[] arrLocal;

        synchronized (this) {
            if (!changed){
                return;
            }
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--){
            ((Observer)arrLocal[i]).update(this, arg);
        }
    }

    /**
     * 將觀察者聚集清空
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * 將“已變化”設(shè)置為true
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * 將“已變化”重置為false
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * 檢測(cè)本對(duì)象是否已變化
     */
    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * Returns the number of observers of this <tt>Observable</tt> object.
     *
     * @return  the number of observers of this object.
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}

這個(gè)類(lèi)代表一個(gè)被觀察者對(duì)象,有時(shí)稱(chēng)之為主題對(duì)象吧慢。一個(gè)被觀察者對(duì)象可以有數(shù)個(gè)觀察者對(duì)象涛漂,每個(gè)觀察者對(duì)象都是實(shí)現(xiàn)Observer接口的對(duì)象。在被觀察者發(fā)生變化時(shí)检诗,會(huì)調(diào)用Observable的notifyObservers()方法匈仗,此方法調(diào)用所有的具體觀察者的update()方法,從而使所有的觀察者都被通知更新自己逢慌。


怎么使用Java對(duì)觀察者模式的支持

這里給出一個(gè)非常簡(jiǎn)單的例子悠轩,說(shuō)明怎樣使用JAVA所提供的對(duì)觀察者模式的支持。在這個(gè)例子中攻泼,被觀察對(duì)象叫做Watched火架;而觀察者對(duì)象叫做Watcher。Watched對(duì)象繼承自java.util.Observable類(lèi)忙菠;而Watcher對(duì)象實(shí)現(xiàn)了java.util.Observer接口何鸡。另外有一個(gè)Test類(lèi)扮演客戶(hù)端角色。

被觀察者Watched類(lèi)源代碼

public class Watched extends Observable{
    
    private String data = "";
    
    public String getData() {
        return data;
    }

    public void setData(String data) {
        
        if(!this.data.equals(data)){
            this.data = data;
            setChanged();
        }
        notifyObservers();
    } 
}

觀察者類(lèi)源代碼

public class Watcher implements Observer{
    
    public Watcher(Observable o){
        o.addObserver(this);
    }
    
    @Override
    public void update(Observable o, Object arg) {
        
        System.out.println("狀態(tài)發(fā)生改變:" + ((Watched)o).getData());
    }
}
public class Test {

    public static void main(String[] args) {
        //創(chuàng)建被觀察者對(duì)象
        Watched watched = new Watched();

        //創(chuàng)建觀察者對(duì)象牛欢,并將被觀察者對(duì)象登記
        Observer watcher = new Watcher(watched);

        //給被觀察者狀態(tài)賦值
        watched.setData("start");
        watched.setData("run");
        watched.setData("stop");
    }

Test對(duì)象首先創(chuàng)建了Watched和Watcher對(duì)象骡男。在創(chuàng)建Watcher對(duì)象時(shí),將Watched對(duì)象作為參數(shù)傳入傍睹;然后Test對(duì)象調(diào)用Watched對(duì)象的setData()方法隔盛,觸發(fā)Watched對(duì)象的內(nèi)部狀態(tài)變化犹菱;Watched對(duì)象進(jìn)而通知實(shí)現(xiàn)登記過(guò)的Watcher對(duì)象,也就是調(diào)用它的update()方法吮炕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腊脱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子龙亲,更是在濱河造成了極大的恐慌陕凹,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俱笛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡传趾,警方通過(guò)查閱死者的電腦和手機(jī)迎膜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浆兰,“玉大人磕仅,你說(shuō)我怎么就攤上這事◆こ剩” “怎么了榕订?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蜕便。 經(jīng)常有香客問(wèn)我劫恒,道長(zhǎng),這世上最難降的妖魔是什么轿腺? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任两嘴,我火速辦了婚禮,結(jié)果婚禮上族壳,老公的妹妹穿的比我還像新娘憔辫。我一直安慰自己,他們只是感情好仿荆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布贰您。 她就那樣靜靜地躺著,像睡著了一般拢操。 火紅的嫁衣襯著肌膚如雪锦亦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,190評(píng)論 1 299
  • 那天令境,我揣著相機(jī)與錄音孽亲,去河邊找鬼。 笑死展父,一個(gè)胖子當(dāng)著我的面吹牛返劲,可吹牛的內(nèi)容都是我干的玲昧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼篮绿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼孵延!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起亲配,我...
    開(kāi)封第一講書(shū)人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤尘应,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后吼虎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體犬钢,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年思灰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玷犹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡洒疚,死狀恐怖歹颓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情油湖,我是刑警寧澤巍扛,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站乏德,受9級(jí)特大地震影響撤奸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喊括,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一寂呛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瘾晃,春花似錦贷痪、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至强胰,卻和暖如春舱沧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背偶洋。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工熟吏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓牵寺,卻偏偏與公主長(zhǎng)得像悍引,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帽氓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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