Java設(shè)計(jì)模式 | 觀察者模式解析與實(shí)戰(zhàn)

概述

  • 觀察者模式是一個(gè)使用率非常高的模式,
    它最常用的地方是 GUI 系統(tǒng)、訂閱——發(fā)布系統(tǒng)蝎宇。

  • 這個(gè)模式的一個(gè)重要作用就是解耦
    被觀察者觀察者解耦祷安,
    使得它們之間的依賴性更小姥芥,甚至做到毫無(wú)依賴。

  • 以GUI系統(tǒng)來(lái)說(shuō)汇鞭,應(yīng)用的UI具有易變性凉唐,
    尤其是前期隨著業(yè)務(wù)的改變或者產(chǎn)品的需求修改,
    應(yīng)用界面也會(huì)經(jīng)常性變化霍骄,但是業(yè)務(wù)邏輯基本變化不大台囱,
    此時(shí),GUI系統(tǒng)需要一套機(jī)制來(lái)應(yīng)對(duì)這種情況读整,
    使得UI層具體的業(yè)務(wù)邏輯解耦玄坦,觀察者模式此時(shí)就派上用場(chǎng)了。

;娉痢<彘埂!
因?yàn)?code>觀察者僅負(fù)責(zé)調(diào)度被觀察者更新方法车伞,
并提供一個(gè)業(yè)務(wù)數(shù)據(jù)給被觀察者择懂;
被觀察者具體實(shí)現(xiàn)更新方法【可以實(shí)現(xiàn)UI、數(shù)據(jù)更新】另玖,
更新方法具體實(shí)現(xiàn)的內(nèi)容觀察者業(yè)務(wù)邏輯基本毫無(wú)依賴困曙!

定義

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

使用場(chǎng)景

●關(guān)聯(lián)行為場(chǎng)景,需要注意的是鳄哭,關(guān)聯(lián)行為是可拆分的要糊,而不是“組合”關(guān)系;
●事件多級(jí)觸發(fā)場(chǎng)景妆丘;
●跨系統(tǒng)的消息交換場(chǎng)景锄俄,如消息隊(duì)列事件總線處理機(jī)制勺拣。

UML類圖

●Subject:抽象主題奶赠,也就是被觀察(Observable)的角色,抽象主題角色把所有觀察者對(duì)象的引用保存在一個(gè)集合里药有,每個(gè)主題都可以有任意數(shù)量的觀察者毅戈,抽象主題提供一個(gè)接口,可以增加和刪除觀察者對(duì)象。
●ConcreteSubject:具體主題苇经,該角色將有關(guān)狀態(tài)存入具體觀察者對(duì)象赘理,在具體主題的內(nèi)部狀態(tài)發(fā)生改變時(shí),給所有注冊(cè)過(guò)的觀察者發(fā)出通知塑陵,具體主題角色又叫做具體被觀察者(Concrete Observable)角色感憾。
●Observer:抽象觀察者蜡励,該角色是觀察者的抽象類令花,它定義了一個(gè)更新接口,使得在得到主題的更改通知時(shí)更新自己凉倚。

觀察者模式實(shí)現(xiàn)思路總結(jié)

觀察者接口準(zhǔn)備更新(數(shù)據(jù)或UI的)方法兼都;
被觀察者接口準(zhǔn)備三個(gè)抽象方法;

觀察者實(shí)現(xiàn)類具體實(shí)現(xiàn)更新邏輯稽寒,可以有參數(shù)扮碧,參數(shù)為更新需要的數(shù)據(jù);

被觀察者實(shí)現(xiàn)類準(zhǔn)備一個(gè)觀察者List以及實(shí)現(xiàn)三個(gè)方法:
1.觀察者注冊(cè)方法:
參數(shù)為某觀察者杏糙,功能是把觀察者參數(shù)加到觀察者List中慎王;
2.注銷觀察者方法:
參數(shù)為某觀察者,功能是把觀察者參數(shù)從觀察者List中移除宏侍;
3.通知觀察者方法:無(wú)參數(shù)或者把需要通知的數(shù)據(jù)作為參數(shù)赖淤,
功能是遍歷所有已注冊(cè)的觀察者,
即遍歷 注冊(cè)添加到 觀察者List中的觀察者谅河,逐個(gè)調(diào)用List中所有觀察者的更新方法咱旱;即一次性更新所有已注冊(cè)的觀察者!

使用時(shí)绷耍,
實(shí)例化一個(gè)被觀察者和若干個(gè)觀察者吐限,
將所有觀察者注冊(cè)到被觀察者處,
調(diào)用被觀察者的通知方法褂始,一次性更新所有已注冊(cè)的觀察者诸典!

案例

來(lái)自 Android | TCP的C(Java|Android)/S(Java)通信實(shí)戰(zhàn)經(jīng)典聊天室案例(文末附本案例代碼實(shí)現(xiàn)概述、觀察者模式實(shí)現(xiàn)小結(jié))

  • 準(zhǔn)備一個(gè)消息隊(duì)列崎苗,
    每一個(gè)Client發(fā)送過(guò)來(lái)的消息搂赋,
    都會(huì)被加入到隊(duì)列當(dāng)中去,
    隊(duì)列中默認(rèn)有一個(gè)子線程益缠,
    專門從隊(duì)列中脑奠,死循環(huán),不斷去取數(shù)據(jù)(取出隊(duì)列的隊(duì)頭)幅慌,
    取到數(shù)據(jù)就做相關(guān)處理宋欺,比如分發(fā)給其他的socket;
/**
 * <pre>
 *     desc   :每一個(gè)Client發(fā)送過(guò)來(lái)的消息,
 *             都會(huì)被加入到隊(duì)列當(dāng)中去齿诞,
 *             隊(duì)列中默認(rèn)有一個(gè)子線程酸休,
 *             專門從隊(duì)列中,死循環(huán)祷杈,不斷去取數(shù)據(jù)斑司,
 *             取到數(shù)據(jù)就做相關(guān)處理,比如分發(fā)給其他的socket但汞;
 * </pre>
 */
public class MsgPool {

    private static MsgPool mInstance = new MsgPool();

    /*
        這里默認(rèn)消息是String類型宿刮,
        或者可以自行封裝一個(gè)Model 類,存儲(chǔ)更詳細(xì)的信息

        block n.塊私蕾; 街區(qū)僵缺;障礙物,阻礙
        顧名思義踩叭,這是一個(gè)阻塞的隊(duì)列磕潮,當(dāng)有消息過(guò)來(lái)時(shí),就把消息發(fā)送給這個(gè)隊(duì)列容贝,
        這邊會(huì)起一個(gè)線程專門從隊(duì)列里面去取消息自脯,
        如果隊(duì)列中沒有消息,就會(huì)阻塞在原地
     */
    private LinkedBlockingQueue<String> mQueue = new LinkedBlockingQueue<>();

    public static MsgPool getInstance() {
        return mInstance;
    }

    private MsgPool() {
    }

    //這是一個(gè)阻塞的隊(duì)列斤富,
    // 當(dāng)有消息過(guò)來(lái)時(shí)膏潮,即客戶端接收到消息時(shí),
    // 就把消息發(fā)送(添加)到這個(gè)隊(duì)列中
    //現(xiàn)在所有的客戶端都可以發(fā)送消息到這個(gè)隊(duì)列中
    public void sendMsg(String msg) {
        try {
            mQueue.put(msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    //要一早就調(diào)用本方法茂缚,
    // 啟動(dòng)這個(gè)讀取消息的線程戏罢,在后臺(tái)不斷運(yùn)行
    public void start() {
        //開啟一個(gè)線程去讀隊(duì)列的數(shù)據(jù)
        new Thread() {
            @Override
            public void run() {
                //無(wú)限循環(huán)讀取信息
                while (true) {
                    try {
                        //取出并移除隊(duì)頭;沒有消息時(shí)脚囊,take()是阻塞的
                        String msg = mQueue.take();
                        notifyMsgComing(msg);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    //被觀察者方法龟糕,遍歷所有已注冊(cè)的觀察者,一次性通知更新
    private void notifyMsgComing(String msg) {
        for (MsgComingListener listener : mListeners) {
            listener.onMsgComing(msg);
        }
    }

    //觀察者接口
    public interface MsgComingListener {
        void onMsgComing(String msg);//更新方法
    }

    //被觀察者悔耘,存放觀察者
    private List<MsgComingListener> mListeners = new ArrayList<>();

    //被觀察者方法讲岁,添加觀察者到列表
    public void addMsgComingListener(MsgComingListener listener) {
        mListeners.add(listener);
    }
}

案例小結(jié):
所有的客戶端都可發(fā)送消息到隊(duì)列中,
然后所有的客戶端都在等待
消息隊(duì)列的消息新增(mQueue.put())這個(gè)時(shí)刻衬以,
消息隊(duì)列一新增消息缓艳,
即一接收到某個(gè)客戶端發(fā)送過(guò)來(lái)消息(mQueue.put()),
則消息都會(huì)一次性轉(zhuǎn)發(fā)給所有客戶端看峻,
所以這里涉及到一個(gè)觀察者設(shè)計(jì)模式阶淘,
消息隊(duì)列(MsgPool)或消息(Msg)是被觀察者,
所有客戶端處理線程(ClientTask)都是觀察者










參考:

  • 《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》
  • 慕課網(wǎng)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末互妓,一起剝皮案震驚了整個(gè)濱河市溪窒,隨后出現(xiàn)的幾起案子坤塞,更是在濱河造成了極大的恐慌,老刑警劉巖澈蚌,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摹芙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡宛瞄,警方通過(guò)查閱死者的電腦和手機(jī)浮禾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)份汗,“玉大人盈电,你說(shuō)我怎么就攤上這事÷阌埃” “怎么了挣轨?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵军熏,是天一觀的道長(zhǎng)轩猩。 經(jīng)常有香客問(wèn)我,道長(zhǎng)荡澎,這世上最難降的妖魔是什么均践? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮摩幔,結(jié)果婚禮上彤委,老公的妹妹穿的比我還像新娘。我一直安慰自己或衡,他們只是感情好焦影,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著封断,像睡著了一般斯辰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坡疼,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天彬呻,我揣著相機(jī)與錄音,去河邊找鬼柄瑰。 笑死闸氮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的教沾。 我是一名探鬼主播蒲跨,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼授翻!你這毒婦竟也來(lái)了或悲?” 一聲冷哼從身側(cè)響起镐作,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隆箩,沒想到半個(gè)月后该贾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捌臊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年杨蛋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片理澎。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逞力,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出糠爬,到底是詐尸還是另有隱情寇荧,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布执隧,位于F島的核電站揩抡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏镀琉。R本人自食惡果不足惜峦嗤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屋摔。 院中可真熱鬧烁设,春花似錦、人聲如沸钓试。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)弓熏。三九已至恋谭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硝烂,已是汗流浹背箕别。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留滞谢,地道東北人串稀。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像狮杨,于是被迫代替她去往敵國(guó)和親母截。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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