觀察者模式

觀察者模式

我知道android的listview是用的觀察者模式,Eventbus也是用的觀察者模式籍琳。但是我還是不能靈活的使用這種設(shè)計(jì)模式菲宴。所以我想整理一下這種設(shè)計(jì)模式,以便加深對(duì)于這種設(shè)計(jì)模式的理解趋急。

什么是觀察者模式喝峦,觀察者模式是用來(lái)做什么的?
每種設(shè)計(jì)模式都是用于解決軟件開(kāi)發(fā)中的一些通用問(wèn)題產(chǎn)生的呜达,觀察者模式同樣如此谣蠢。

這里引入一下 (“Java與模式“書(shū)中的話(huà)):
觀察者模式是對(duì)象行為模式,又叫發(fā)布-訂閱(publish/Subscribc模式)查近、模型與-視圖(Model/View)模式眉踱。

觀察者模式定義了一種一對(duì)多的依賴(lài)關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽(tīng)某一個(gè)主題對(duì)象霜威,這個(gè)主題對(duì)象在狀態(tài)上發(fā)生變化時(shí)谈喳,會(huì)通知所有的觀察者對(duì)象,使它們能夠自動(dòng)更新自己戈泼。

加粗的這句我覺(jué)得是觀察者模式解決的軟件開(kāi)發(fā)問(wèn)題婿禽,需要重點(diǎn)理解一下。 結(jié)合我所知道的設(shè)計(jì)的模式的使用可以這樣理解大猛,listview需要監(jiān)聽(tīng)數(shù)據(jù)的變化扭倾,來(lái)更新item的顯示, 而Eventbus需要根據(jù)用戶(hù)發(fā)布的事件挽绩,來(lái)通知所有的訂閱者來(lái)做出相應(yīng)的處理膛壹。

所以在軟件開(kāi)發(fā)的過(guò)程中,遇到多個(gè)對(duì)象依賴(lài)一個(gè)對(duì)象的情況琼牧,就需要考慮引入觀察者模式恢筝。

說(shuō)了理解,下面整理一下用java代碼實(shí)現(xiàn)觀察模式, 在寫(xiě)代碼之前呢巨坊,我們可以想象一下撬槽,如果要用java代碼實(shí)現(xiàn)觀察者模式要怎么設(shè)計(jì)呢。 首先整理一下功能需求趾撵,我們要實(shí)現(xiàn)的功能就是觀察者模式的定義侄柔。

觀察者模式定義了一種一對(duì)多的依賴(lài)關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽(tīng)某一個(gè)主題對(duì)象占调,這個(gè)主題對(duì)象在狀態(tài)上發(fā)生變化時(shí)暂题,會(huì)通知所有的觀察者對(duì)象,使它們能夠自動(dòng)更新自己究珊。

簡(jiǎn)單點(diǎn)說(shuō)薪者,其實(shí)要實(shí)現(xiàn)的就是,當(dāng)一個(gè)對(duì)象發(fā)生改變的時(shí)候剿涮,我們要通知和他相關(guān)聯(lián)的對(duì)象也跟隨著進(jìn)行改變言津。那我們可以把發(fā)生改變的對(duì)象稱(chēng)為被觀察者,跟隨改變的對(duì)象稱(chēng)為觀察者取试。 那其實(shí)可以想象的到闺骚, 被觀察者對(duì)象類(lèi)中應(yīng)該有一個(gè)集合舶沿,里面是我們所有的觀察者對(duì)象,當(dāng)被觀察者對(duì)象發(fā)生改變的時(shí)候,遍歷集合中的所有的觀察者然后通知他們進(jìn)行同步的改變兢卵。 所以我們的觀察者對(duì)象中應(yīng)該有一個(gè)更新方法。 再回到被觀察者對(duì)象节吮,既然類(lèi)中有個(gè)集合保存所有需要通知的觀察者残邀,那么是不是應(yīng)該有一個(gè)添加觀察者,移除觀察者的方法呢铺根。 還有就是如果要通過(guò)觀察者進(jìn)行改變宪躯,那是不是應(yīng)該要有個(gè)標(biāo)識(shí)呢。

好位迂,總結(jié)一下访雪,觀察者和被觀察者類(lèi)中應(yīng)該有的方法:
觀察者: 同步進(jìn)行更新的方法
被觀察者: 觀察者集合, 添加觀察者的方法掂林,移除觀察者的方法臣缀,標(biāo)識(shí)是否更新的方法,通知所有的觀察者更新的方法泻帮。

那精置,既然所有的觀察者,被觀察者都應(yīng)該有以上方法锣杂,那我們是不是應(yīng)該把他們提取出來(lái)呢脂倦。 很機(jī)智啊番宁,有沒(méi)有!

其他JDK已經(jīng)幫我們封裝好了赖阻。在JAVA語(yǔ)言的java.util庫(kù)里面蝶押,提供了一個(gè)Observable類(lèi)以及一個(gè)Observer接口。其中Observable類(lèi)就對(duì)應(yīng)著我們的被觀察者抽象類(lèi)火欧。Observer接口就對(duì)應(yīng)著觀察者抽象類(lèi)棋电。 方法和我們總結(jié)的很類(lèi)似,就直接上代碼了苇侵。
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();
    }
}

下面我們進(jìn)入正題藕咏,要怎么基于目前的抽象類(lèi),實(shí)現(xiàn)觀察者模式:
想向一個(gè)業(yè)務(wù)場(chǎng)景秽五,日常開(kāi)發(fā)的過(guò)程中孽查,我們都會(huì)訂閱一些技術(shù)網(wǎng)站,向開(kāi)發(fā)者頭條之類(lèi)的坦喘。而每次他們發(fā)布新消息的時(shí)候我們就能同步的收到消息盲再。就其實(shí)就類(lèi)似一種觀察者模式。 我們是觀察者瓣铣, 技術(shù)網(wǎng)站是被觀察者答朋。上代碼理解一下:

/**
 * 程序員是觀察者
 *
 * @author mrsimple
 */
public class Coder implements Observer {
    public String name ;

    public Coder(String aName) {
        name = aName ;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println( "Hi, " +  name + ", 開(kāi)發(fā)者頭條更新啦, 內(nèi)容 : " + arg);
    }

    @Override
    public String toString() {
        return "碼農(nóng) : " + name;
    }

}

可以看到Coder 是觀察者,實(shí)現(xiàn)了Observer 接口棠笑。 然后等被觀察者梦碗,同步更新?tīng)顟B(tài)的時(shí)候會(huì)調(diào)用update方法。

/**
 * AndroidWeekly這個(gè)網(wǎng)站是被觀察者,它有更新所有的觀察者 (這里是程序員) 都會(huì)接到相應(yīng)的通知.
 *
 * @author mrsimple
 */
public class DeveloperNews extends Observable {

    public void postNewPublication(String content) {
        // 標(biāo)識(shí)狀態(tài)或者內(nèi)容發(fā)生改變
        setChanged();
        // 通知所有觀察者
        notifyObservers(content);
    }

}

DeveloperNews 是被觀察者對(duì)象,繼承了Observable 類(lèi)洪规, 當(dāng)每次發(fā)布新的新聞的時(shí)候印屁,調(diào)用postNewPublication方法,而在postNewPublication中調(diào)用基類(lèi)的setChanged斩例,notifyObservers兩個(gè)方法库车。去通知所有的觀察者進(jìn)行更新?tīng)顟B(tài)。

測(cè)試代碼:

  public static void main(String[] args) throws Exception {
    // 被觀察的角色
    DeveloperNews androidWeekly = new DeveloperNews();
    // 觀察者
    Coder coder0 = new Coder("coder-0");
    Coder coder1 = new Coder("coder-1");
    Coder coder2 = new Coder("coder-2");
    Coder coder3 = new Coder("coder-3");

    // 將觀察者注冊(cè)到可觀察對(duì)象的觀察者列表中
    androidWeekly.addObserver(coder0);
    androidWeekly.addObserver(coder1);
    androidWeekly.addObserver(coder2);
    androidWeekly.addObserver(coder3);

    // 發(fā)布消息
    androidWeekly.postNewPublication("新的一期AndroidWeekly來(lái)啦!");
}

輸出:

Hi, coder-3, 開(kāi)發(fā)者頭條更新啦, 內(nèi)容 : 新的一期AndroidWeekly來(lái)啦!
Hi, coder-2, 開(kāi)發(fā)者頭條更新啦, 內(nèi)容 : 新的一期AndroidWeekly來(lái)啦!
Hi, coder-1, 開(kāi)發(fā)者頭條更新啦, 內(nèi)容 : 新的一期AndroidWeekly來(lái)啦!
Hi, coder-0, 開(kāi)發(fā)者頭條更新啦, 內(nèi)容 : 新的一期AndroidWeekly來(lái)啦!

觀察者模式的理解和使用目前我就總結(jié)了這些樱拴,以后應(yīng)該會(huì)補(bǔ)充更多的使用心得。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末洋满,一起剝皮案震驚了整個(gè)濱河市晶乔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌牺勾,老刑警劉巖正罢,帶你破解...
    沈念sama閱讀 216,919評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異驻民,居然都是意外死亡翻具,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)回还,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)裆泳,“玉大人,你說(shuō)我怎么就攤上這事柠硕」ず蹋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,316評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蝗柔,是天一觀的道長(zhǎng)闻葵。 經(jīng)常有香客問(wèn)我,道長(zhǎng)癣丧,這世上最難降的妖魔是什么槽畔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,294評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮胁编,結(jié)果婚禮上厢钧,老公的妹妹穿的比我還像新娘。我一直安慰自己掏呼,他們只是感情好坏快,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著憎夷,像睡著了一般莽鸿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,245評(píng)論 1 299
  • 那天祥得,我揣著相機(jī)與錄音兔沃,去河邊找鬼。 笑死级及,一個(gè)胖子當(dāng)著我的面吹牛乒疏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播饮焦,決...
    沈念sama閱讀 40,120評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼怕吴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了县踢?” 一聲冷哼從身側(cè)響起转绷,我...
    開(kāi)封第一講書(shū)人閱讀 38,964評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎硼啤,沒(méi)想到半個(gè)月后议经,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,376評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谴返,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評(píng)論 2 333
  • 正文 我和宋清朗相戀三年煞肾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗓袱。...
    茶點(diǎn)故事閱讀 39,764評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡籍救,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渠抹,到底是詐尸還是另有隱情钧忽,我是刑警寧澤,帶...
    沈念sama閱讀 35,460評(píng)論 5 344
  • 正文 年R本政府宣布逼肯,位于F島的核電站耸黑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏篮幢。R本人自食惡果不足惜大刊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望三椿。 院中可真熱鬧缺菌,春花似錦、人聲如沸搜锰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,697評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛋叼。三九已至焊傅,卻和暖如春剂陡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狐胎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,846評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工鸭栖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人握巢。 一個(gè)月前我還...
    沈念sama閱讀 47,819評(píng)論 2 370
  • 正文 我出身青樓晕鹊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親暴浦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子溅话,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評(píng)論 2 354

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