觀察者模式

什么是觀察者模式雹洗?

概念:定義對(duì)象間一種一對(duì)多的依賴關(guān)系,使得每當(dāng)一個(gè)對(duì)象改變狀態(tài),則所有依賴于它的對(duì)象都會(huì)得到通知并被自動(dòng)更新张肾。

說(shuō)白了就是一個(gè)或多個(gè)觀察者同時(shí)可以觀察一個(gè)被觀察者管宵,當(dāng)被觀察者發(fā)生變化截珍,所有的觀察者都會(huì)收到通知,做相應(yīng)的工作箩朴。一個(gè)最簡(jiǎn)單的例子岗喉,多個(gè)警察盯上一個(gè)小偷,在小偷進(jìn)行偷竊行為時(shí)炸庞,警察們收到信號(hào)實(shí)施抓捕钱床。

為什么要用觀察者模式?

當(dāng)我們?cè)谝粋€(gè)模塊中監(jiān)聽另一個(gè)模塊的事件埠居,而又不想讓它們之間有依賴性查牌,就可以選擇觀察者模式事期。

簡(jiǎn)單實(shí)現(xiàn)

例如簡(jiǎn)書公眾號(hào)不時(shí)推送一些優(yōu)秀文章,只要關(guān)注了它就可以收到推送纸颜,這里我們用戶是觀察者兽泣,簡(jiǎn)書公眾號(hào)就是被觀察者。

/**
 * 觀察者
 */
public class User implements Observer {
    private String name;

    public User(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        // 收到事件通知
        System.out.println("Hi," + name + ", 推薦一篇優(yōu)秀文章胁孙, 內(nèi)容:" + arg);
    }
}
/**
 * 被觀察者
 */
public class JianShuSubscription extends Observable {

    public void pushArticle(String content){
        // 標(biāo)識(shí)狀態(tài)或者內(nèi)容發(fā)生改變
        setChanged();
        // 通知所有觀察者
        notifyObservers(content);
    }
}
public class Test {
    public static void main(String[] args){
        // 新建被觀察者
        JianShuSubscription jianShuSubscription = new JianShuSubscription();
        // 觀察者
        User a = new User("A");
        User b = new User("B");
        User c = new User("C");
        User d = new User("D");
        
        // 將觀察者注冊(cè)到可觀察對(duì)象的觀察者列表中
        jianShuSubscription.addObserver(a);
        jianShuSubscription.addObserver(b);
        // 重復(fù)注冊(cè)
        jianShuSubscription.addObserver(b);
        jianShuSubscription.addObserver(c);
        jianShuSubscription.addObserver(d);
        
        // 發(fā)布消息
        jianShuSubscription.pushArticle("觀察者模式");
    }
}

運(yùn)行結(jié)果:

觀察者模式.png

可見(jiàn)重復(fù)注冊(cè)也只會(huì)通知一次唠倦,我們來(lái)看看java.util包下的這兩個(gè)類Observer和Observable:

package java.util;

/**
 * @author  Chris Warth
 * @see     java.util.Observable
 * @since   JDK1.0
 */
public interface Observer {
    /**
     * Observable調(diào)用notifyObservers()方法時(shí)被調(diào)用
     * @param   o     被觀察的對(duì)象
     * @param   arg   傳過(guò)來(lái)的信息
     */
    void update(Observable o, Object arg);
}

Observer類中只有一個(gè)update方法,在Observable調(diào)用notifyObservers()方法時(shí)被調(diào)用涮较,那我們看一下Observable中的notifyObservers()的方法實(shí)現(xiàn)稠鼻。

package java.util;


public class Observable {
    // 是否被改變標(biāo)識(shí)
    private boolean changed = false;
    // 觀察者集合
    private Vector<Observer> obs;

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

    /**
     * 添加
     * @param o
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        // 防止觀察者重復(fù)
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    /**
     * 刪除
     * @param o
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    /**
     * 通知所有的觀察者,不攜帶信息
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * 通知所有的觀察者狂票,攜帶信息arg
     */
    public void notifyObservers(Object arg) {
        // 臨時(shí)緩沖區(qū)候齿,保存當(dāng)時(shí)的觀察者集合
        Object[] arrLocal;

        // 這里加鎖顯然是為了線程安全,但是可能會(huì)有2種不希望的結(jié)果
        // 1苫亦、最新添加的觀察者無(wú)法接收到這個(gè)通知
        // 2毛肋、最近反注冊(cè)掉的觀察者也能接收到這個(gè)通知
        synchronized (this) {
            // 判斷改變標(biāo)識(shí)
            if (!hasChanged())
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            // 逐一調(diào)用觀察者的update()方法
            ((Observer)arrLocal[i]).update(this, arg);
    }

    /**
     * 清除所有觀察者
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * 把被觀察者標(biāo)識(shí)設(shè)為改變狀態(tài):我準(zhǔn)備發(fā)通知了
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * 我已經(jīng)確認(rèn)發(fā)過(guò)通知了
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * 判斷此時(shí)是不是正在發(fā)通知
     */
    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * 觀察者數(shù)量
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}

上面注釋已經(jīng)寫得很清楚了,被觀察者發(fā)送通知時(shí)需要調(diào)用2個(gè)方法:

  1. setChanged() 改變標(biāo)志位屋剑,準(zhǔn)備發(fā)通知润匙。
  2. notifyObservers(content) 通知所有的觀察者。

缺點(diǎn):

觀察者模式優(yōu)點(diǎn)是:觀察者和被觀察者之間是抽象耦合唉匾,沒(méi)有直接依賴關(guān)系孕讳;增強(qiáng)系統(tǒng)靈活性、可擴(kuò)展性巍膘。
在應(yīng)用觀察者模式時(shí)需要考慮一下開發(fā)效率和運(yùn)行效率問(wèn)題厂财,程序中包括一個(gè)被觀察者、多個(gè)觀察者峡懈,開發(fā)和調(diào)試等內(nèi)容會(huì)比較復(fù)雜璃饱,而且在Java種消息的通知默認(rèn)時(shí)順序執(zhí)行,一個(gè)觀察者卡頓肪康,會(huì)影響整體的運(yùn)行效率荚恶,在這種情況下,要考慮采用異步的方式磷支。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谒撼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子雾狈,更是在濱河造成了極大的恐慌廓潜,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異辩蛋,居然都是意外死亡呻畸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門堪澎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)擂错,“玉大人,你說(shuō)我怎么就攤上這事樱蛤。” “怎么了剑鞍?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵昨凡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蚁署,道長(zhǎng)便脊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任光戈,我火速辦了婚禮哪痰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘久妆。我一直安慰自己晌杰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布筷弦。 她就那樣靜靜地躺著肋演,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烂琴。 梳的紋絲不亂的頭發(fā)上爹殊,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音奸绷,去河邊找鬼梗夸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛号醉,可吹牛的內(nèi)容都是我干的反症。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼扣癣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼惰帽!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起父虑,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤该酗,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呜魄,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悔叽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爵嗅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娇澎。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖睹晒,靈堂內(nèi)的尸體忽然破棺而出趟庄,到底是詐尸還是另有隱情,我是刑警寧澤伪很,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布戚啥,位于F島的核電站,受9級(jí)特大地震影響锉试,放射性物質(zhì)發(fā)生泄漏猫十。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一呆盖、第九天 我趴在偏房一處隱蔽的房頂上張望拖云。 院中可真熱鬧,春花似錦应又、人聲如沸宙项。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)杉允。三九已至,卻和暖如春席里,著一層夾襖步出監(jiān)牢的瞬間叔磷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工奖磁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留改基,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓咖为,卻偏偏與公主長(zhǎng)得像秕狰,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子躁染,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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

  • Java運(yùn)行時(shí)環(huán)境內(nèi)置了垃圾收集(GC)模塊. 上一代的很多編程語(yǔ)言中并沒(méi)有自動(dòng)內(nèi)存回收機(jī)制, 需要程序員手工編寫...
    sherlock_6981閱讀 686評(píng)論 0 0
  • Option 1# 15 Central Park West #38A ↓ $29,950,000 2,846 f...
    海瀾之家閱讀 1,063評(píng)論 0 0
  • 古語(yǔ)名句鸣哀,道盡人世間哲理 1.它山之石,可以攻玉——《詩(shī)經(jīng).鶴鳴》 2.瓜田不納履,李下不正冠——漢樂(lè)府民歌《君子...
    敲冰求火閱讀 486評(píng)論 0 1
  • 我住進(jìn)了魏王府左側(cè)清幽寂靜的院落里。走入黑漆的大門里吞彤,你會(huì)發(fā)現(xiàn)里面別有洞天我衬。 這座被稱為“聽雨軒”的院子叹放,從外面看...
    煙曉墨痕閱讀 238評(píng)論 0 0