016.觀察者模式

李斯和韓非子都是荀子的學生胡桨,李斯是師兄,韓非子是師弟瞬雹,若干年后昧谊,李斯成為秦國的上尉,致力于統(tǒng)一全國酗捌,于是安插了間諜到各個國家的重要人物的身邊進行監(jiān)視呢诬,韓非子身邊也有很多間諜,韓非子早飯吃的什么胖缤,晚上在做什么娛樂尚镰,李斯都了如指掌,我們先通過程序把這個過程展現(xiàn)一下哪廓,看看李斯是怎么監(jiān)控韓非子的狗唉,先看類圖:

程序實現(xiàn):

public interface IHanFeiZi {

    // 吃早飯
    void haveBreakfast();

    // 娛樂
    void haveFun();

}

/**
 * 被觀察者
 */
public class HanFeiZi implements IHanFeiZi {

    // 韓非子是否在吃飯
    private boolean isHaveBreakfast = false;
    // 韓非子是否在娛樂
    private boolean isHaveFun = false;

    @Override
    public void haveBreakfast() {
        System.out.println("韓非子: 開始吃飯...");
        isHaveBreakfast = true;
    }

    @Override
    public void haveFun() {
        System.out.println("韓非子: 開始娛樂...");
        isHaveFun = true;
    }

    public boolean isHaveBreakfast() {
        return this.isHaveBreakfast;
    }

    public boolean isHaveFun() {
        return this.isHaveFun;
    }

    public void setHaveBreakfast(boolean haveBreakfast) {
        isHaveBreakfast = haveBreakfast;
    }

    public void setHaveFun(boolean haveFun) {
        isHaveFun = haveFun;
    }
}

package p16_observer_pattern.version1;

public interface ILiSi {

    // 發(fā)現(xiàn)別人有動靜,自己也要行動起來
    void update(String context);

}

/**
 * 觀察者
 */
public class LiSi implements ILiSi {

    // 首先李斯是個觀察者涡真,一旦韓非子有活動分俯,他就要向老板匯報
    @Override
    public void update(String context) {
        System.out.println("李斯: 觀察到韓非子活動肾筐,向秦始皇匯報...");
        report(context);
    }

    public void report(String reportContext) {
        System.out.println("李斯: 報告,韓非子有活動了: " + reportContext);
    }
}

package p16_observer_pattern.version1;

/**
 * 監(jiān)控程序
 */
public class Watch extends Thread {

    private HanFeiZi hanFeiZi;
    private LiSi liSi;
    private String type;

    public Watch(HanFeiZi hanFeiZi, LiSi liSi, String type) {
        this.hanFeiZi = hanFeiZi;
        this.liSi = liSi;
        this.type = type;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 監(jiān)控是否在吃早飯
            if ("breakfast".equals(type)) {
                // 發(fā)現(xiàn)韓非子在吃飯缸剪,就通知李斯
                if (hanFeiZi.isHaveBreakfast()) {
                    liSi.update("韓非子在吃早飯");
                    // 重置狀態(tài)吗铐,繼續(xù)監(jiān)控
                    hanFeiZi.setHaveBreakfast(false);
                }
            // 監(jiān)控是否在娛樂
            } else {
                // 發(fā)現(xiàn)韓非子在娛樂,就通知李斯
                if (hanFeiZi.isHaveFun()) {
                    liSi.update("韓非子在娛樂");
                    // 重置狀態(tài)杏节,繼續(xù)監(jiān)控
                    hanFeiZi.setHaveFun(false);
                }
            }
        }
    }

}

public class Client {

    public static void main(String[] args) throws InterruptedException {

        LiSi liSi = new LiSi();
        HanFeiZi hanFeiZi = new HanFeiZi();

        // 觀察早餐
        Watch watchBreakfast = new Watch(hanFeiZi, liSi, "breakfast");
        watchBreakfast.start();
        // 觀察娛樂
        Watch watchFun = new Watch(hanFeiZi, liSi, "fun");
        watchFun.start();

        // 韓非子吃飯
        Thread.sleep(1000);
        hanFeiZi.haveBreakfast();

        // 韓非子娛樂
        Thread.sleep(1000);
        hanFeiZi.haveFun();

    }

}

運行Client程序后唬渗,控制臺打印出如下信息:

韓非子: 開始吃飯...
李斯: 觀察到韓非子活動,向秦始皇匯報...
李斯: 報告奋渔,韓非子有活動了: 韓非子在吃早飯
韓非子: 開始娛樂...
李斯: 觀察到韓非子活動镊逝,向秦始皇匯報...
李斯: 報告,韓非子有活動了: 韓非子在娛樂

以上程序的問題:

  • 使用死循環(huán)while(true)來做監(jiān)聽嫉鲸,占用大量CPU資源
  • 不是面向對象的程序

改進蹋半,首先修改類圖:

類圖非常簡單,就是在HanFeiZi類中引用了ILiSi這個接口充坑,看代碼變化:

public class HanFeiZi implements IHanFeiZi {

    // 引用李斯接口
    private ILiSi liSi = new LiSi();

    @Override
    public void haveBreakfast() {
        System.out.println("韓非子: 開始吃飯...");
        // 通知李斯
        liSi.update("韓非子在吃飯");
    }

    @Override
    public void haveFun() {
        System.out.println("韓非子: 開始娛樂...");
        // 通知李斯
        liSi.update("韓非子在娛樂");
    }

}

public class Client {

    public static void main(String[] args) throws InterruptedException {

        HanFeiZi hanFeiZi = new HanFeiZi();

        // 韓非子吃飯
        hanFeiZi.haveBreakfast();

        // 韓非子娛樂
        hanFeiZi.haveFun();

    }

}

運行結果正確减江,效率也提升了。

代碼還是可以繼續(xù)改善捻爷,首先辈灼,韓非子并不是只有李斯在監(jiān)控,再者也榄,韓非子并非只有吃飯和娛樂兩個活動需要監(jiān)視巡莹,為了很好的拓展程序,我們修改類圖如下:

我們把接口名稱修改了一下甜紫,這樣顯得更抽象化降宅,Observable是被觀察者,就是類似韓非子這樣的人囚霸,Observer接口是觀察者腰根,類似李斯這樣的,同時還有其他國家的比如王斯拓型、劉斯等额嘿,在Observable接口中有三個比較重要的方法,分別是addObserver()增加觀察者劣挫,deleteObserver()刪除觀察者册养,notifyObservers()通知所有的觀察者,這是什么意思呢压固?我這里有一個信息球拦,一個對象,我可以允許有多個對象來察看,你觀察也成坎炼,我觀察也成愧膀,只要是觀察者就成,也就是說我的改變或動作執(zhí)行点弯,會通知其他的對象扇调,看程序會更明白一點:

/**
 * 所有被觀察者的通用接口
 */
public interface Observable {

    // 增加觀察者
    void addObserver(Observer observer);

    // 刪除觀察者
    void deleteObserver(Observer observer);

    // 通知所有觀察者
    void notifyObservers(String context);

}

public class HanFeiZi implements Observable {

    // 所有的觀察者
    private List<Observer> observerList = new ArrayList<>();

    @Override
    public void addObserver(Observer observer) {
        observerList.add(observer);
    }

    @Override
    public void deleteObserver(Observer observer) {
        observerList.remove(observer);
    }

    @Override
    public void notifyObservers(String context) {
        for (Observer observer : observerList) {
            observer.update(context);
        }
    }

    public void haveBreakfast() {
        System.out.println("韓非子: 開始吃飯...");
        notifyObservers("韓非子在吃飯");
    }

    public void haveFun() {
        System.out.println("韓非子: 開始娛樂...");
        notifyObservers("韓非子在娛樂");
    }

}

/**
 * 所有觀察者的通用接口
 */
public interface Observer {

    void update(String context);

}

/**
 * 李斯是秦國的觀察者
 */
public class LiSi implements Observer {

    @Override
    public void update(String context) {
        System.out.println("李斯: 觀察到韓非子活動矿咕,向秦王匯報...");
        report(context);
    }

    public void report(String reportContext) {
        System.out.println("李斯: 報告秦王抢肛,韓非子有活動了: " + reportContext);
    }

}

/**
 * 劉斯是齊國的觀察者
 */
public class LiuSi implements Observer {

    @Override
    public void update(String context) {
        System.out.println("劉斯: 觀察到韓非子活動,向齊王匯報...");
        report(context);
    }

    public void report(String reportContext) {
        System.out.println("劉斯: 報告齊王碳柱,韓非子有活動了: " + reportContext);
    }

}

/**
 * 王斯是楚國的觀察者
 */
public class WangSi implements Observer {

    @Override
    public void update(String context) {
        System.out.println("王斯: 觀察到韓非子活動捡絮,向楚王匯報...");
        report(context);
    }

    public void report(String reportContext) {
        System.out.println("王斯: 報告楚王,韓非子有活動了: " + reportContext);
    }

}

public class Client {

    public static void main(String[] args) {

        Observer liSi =  new LiSi();
        Observer wangSi =  new WangSi();
        Observer liuSi =  new LiuSi();

        HanFeiZi hanFeiZi = new HanFeiZi();
        hanFeiZi.addObserver(liSi);
        hanFeiZi.addObserver(wangSi);
        hanFeiZi.addObserver(liuSi);

        hanFeiZi.haveBreakfast();

    }

}

Client的運行結果如下:

韓非子: 開始吃飯...
李斯: 觀察到韓非子活動莲镣,向秦王匯報...
李斯: 報告秦王福稳,韓非子有活動了: 韓非子在吃飯
王斯: 觀察到韓非子活動,向楚王匯報...
王斯: 報告楚王瑞侮,韓非子有活動了: 韓非子在吃飯
劉斯: 觀察到韓非子活動的圆,向齊王匯報...
劉斯: 報告齊王,韓非子有活動了: 韓非子在吃飯

好了半火,結果也正確了越妈,也符合開閉原則了,也同時實現(xiàn)類間解耦钮糖,想再加觀察者梅掠,就繼續(xù)實現(xiàn)Observer接口就成了,但是店归,我們還有一個優(yōu)化點:HanFeiZi 這個實現(xiàn)類中應該抽象出一個父類阎抒,父類完全實現(xiàn)接口,HanFeiZi這個類只實現(xiàn)兩個方法haveBreakfast()haveFun() 就可以了消痛。另外且叁,JDK中提供了java.util.Observable類和 java.util.Observer接口,也就是說我們上面寫的那個例子中的Observable接口可以改換成java.util.Observale類秩伞,Observer接口可以換成java.util.Observer接口谴古,看如下類圖:

代碼實現(xiàn)如下:

import java.util.Observable;

public class HanFeiZi extends Observable {

    public void haveBreakfast() {
        System.out.println("韓非子: 開始吃飯...");
        // 通知所有觀察者
        super.setChanged();
        super.notifyObservers("韓非子在吃飯");
    }

    public void haveFun() {
        System.out.println("韓非子: 開始娛樂...");
        super.setChanged();
        super.notifyObservers("韓非子在娛樂");
    }

}

/* ---------------------------------------------------------------------------------------------------------*/

import java.util.Observable;
import java.util.Observer;

public class LiSi implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("李斯: 觀察到韓非子活動,向秦王匯報...");
        report(arg.toString());
    }

    public void report(String reportContext) {
        System.out.println("李斯: 報告秦王稠歉,韓非子有活動了: " + reportContext);
    }

}

/* ---------------------------------------------------------------------------------------------------------*/

import java.util.Observable;
import java.util.Observer;

public class LiuSi implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("劉斯: 觀察到韓非子活動掰担,向齊王匯報...");
        report(arg.toString());
    }

    public void report(String reportContext) {
        System.out.println("劉斯: 報告齊王,韓非子有活動了: " + reportContext);
    }

}

/* ---------------------------------------------------------------------------------------------------------*/

import java.util.Observable;
import java.util.Observer;

public class WangSi implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("王斯: 觀察到韓非子活動怒炸,向楚王匯報...");
        report(arg.toString());
    }

    public void report(String reportContext) {
        System.out.println("王斯: 報告楚王带饱,韓非子有活動了: " + reportContext);
    }

}

/* ---------------------------------------------------------------------------------------------------------*/

import java.util.Observer;

public class Client {

    public static void main(String[] args) {

        Observer liSi =  new LiSi();
        Observer wangSi =  new WangSi();
        Observer liuSi =  new LiuSi();

        HanFeiZi hanFeiZi = new HanFeiZi();
        hanFeiZi.addObserver(liSi);
        hanFeiZi.addObserver(wangSi);
        hanFeiZi.addObserver(liuSi);

        hanFeiZi.haveBreakfast();

    }

}

程序運行結果上面的運行是結果是一模一樣的,程序已經(jīng)簡約到極致了。這就是觀察者模式勺疼,通用類圖如下:

觀察者模式在什么情況下使用呢教寂?觀察者可以實現(xiàn)消息的廣播,一個消息可以觸發(fā)多個事件执庐,這是觀察者模式非常重要的功能酪耕。使用觀察者模式也有兩個重點問題要解決:

  • 廣播鏈的問題。

    如果你做過數(shù)據(jù)庫的觸發(fā)器轨淌,你就應該知道有一個觸發(fā)器鏈的問題迂烁,比如表 A 上寫了一個觸發(fā)器,內(nèi)容是一個字段更新后更新表 B 的一條數(shù)據(jù)递鹉,而表 B 上也有個觸發(fā)器盟步,要更新表 C,表 C 也有觸發(fā)器…

    我們的觀察者模式也是一樣的問題躏结,一個觀察者可以有雙重身份却盘,既是觀察者,也是被觀察者媳拴,這沒什么問題黄橘,但是鏈一旦建立,這個邏輯就比較復雜屈溉,可維護性非常差塞关,根據(jù)經(jīng)驗建議,在一個觀察者模式中最多出現(xiàn)一個對象既是觀察者也是被觀察者语婴,也就是說消
    息最多轉發(fā)一次(傳遞兩次)描孟,這還是比較好控制的;

  • 異步處理問題砰左。

    被觀察者發(fā)生動作了匿醒,觀察者要做出回應,如果觀察者比較多缠导,而且處理時間比較長怎么辦廉羔?那就用異步唄,異步處理就要考慮線程安全和隊列的問題僻造。

本文原書:

《您的設計模式》 作者:CBF4LIFE

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末憋他,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子髓削,更是在濱河造成了極大的恐慌竹挡,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件立膛,死亡現(xiàn)場離奇詭異揪罕,居然都是意外死亡梯码,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門好啰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轩娶,“玉大人,你說我怎么就攤上這事框往■悖” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵椰弊,是天一觀的道長许溅。 經(jīng)常有香客問我,道長男应,這世上最難降的妖魔是什么闹司? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任娱仔,我火速辦了婚禮沐飘,結果婚禮上,老公的妹妹穿的比我還像新娘牲迫。我一直安慰自己耐朴,他們只是感情好,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布盹憎。 她就那樣靜靜地躺著筛峭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪陪每。 梳的紋絲不亂的頭發(fā)上影晓,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音檩禾,去河邊找鬼挂签。 笑死,一個胖子當著我的面吹牛盼产,可吹牛的內(nèi)容都是我干的饵婆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼戏售,長吁一口氣:“原來是場噩夢啊……” “哼侨核!你這毒婦竟也來了?” 一聲冷哼從身側響起灌灾,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤搓译,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锋喜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體些己,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了轴总。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片直颅。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖怀樟,靈堂內(nèi)的尸體忽然破棺而出功偿,到底是詐尸還是另有隱情,我是刑警寧澤往堡,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布械荷,位于F島的核電站,受9級特大地震影響虑灰,放射性物質(zhì)發(fā)生泄漏吨瞎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一穆咐、第九天 我趴在偏房一處隱蔽的房頂上張望颤诀。 院中可真熱鬧,春花似錦对湃、人聲如沸崖叫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽心傀。三九已至,卻和暖如春拆讯,著一層夾襖步出監(jiān)牢的瞬間脂男,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工种呐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宰翅,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓陕贮,卻偏偏與公主長得像堕油,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肮之,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

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