Java設(shè)計(jì)模式之觀察者模式

Java設(shè)計(jì)模式之觀察者模式

本文僅是個(gè)人觀點(diǎn),如有錯(cuò)誤請(qǐng)指正

簡(jiǎn)介

當(dāng)對(duì)象間存在一對(duì)多關(guān)系時(shí)暑脆,可以考慮使用觀察者模式(Observer Pattern)赎离。比如,當(dāng)一個(gè)對(duì)象被修改時(shí)气笙,則會(huì)自動(dòng)通知它的依賴對(duì)象。觀察者模式屬于行為型模式怯晕。

實(shí)現(xiàn)

接下來將用三個(gè)例子來說明觀察者模式潜圃,詳細(xì)代碼可以參考文末的地址。

需求

現(xiàn)在有一個(gè)氣象站舟茶,想對(duì)外發(fā)布數(shù)據(jù)谭期,有一個(gè)公司知道后想要通過天氣站的數(shù)據(jù)建立一個(gè)天氣看板,這就交給了公司的開發(fā)小王吧凉,小王拿到需求后隧出,很快就開始行動(dòng)了,下面就是小王的實(shí)現(xiàn)方法

  • 氣象站類阀捅,主要提供溫度鸳劳、壓力、濕度參數(shù)也搓,以及數(shù)據(jù)更新,數(shù)據(jù)通知涵紊,再把需要接收參數(shù)的看板加進(jìn)來指定通知對(duì)象傍妒。

    public class WeatherData {
    
        /**
         * 溫度
         */
        private float mTemperate;
    
        /**
         * 壓力
         */
        private float mPressure;
    
        /**
         * 濕度
         */
        private float mHumidity;
    
        private CurrentConditions mCurrentConditions;
    
        /**
         * 加入需要通知的看板
         * @param mCurrentConditions
         */
        public WeatherData(CurrentConditions mCurrentConditions) {
            this.mCurrentConditions = mCurrentConditions;
        }
    
        public float getTemperature() {
            return mTemperate;
    
        }
    
        public float getPressure() {
            return mPressure;
    
        }
    
        public float getHumidity() {
            return mHumidity;
    
        }
    
        /**
         * 通知數(shù)據(jù)修改
         */
        public void dataChange() {
            mCurrentConditions.update(getTemperature(), getPressure(), getHumidity());
        }
    
        /**
         * 數(shù)據(jù)修改 同時(shí)調(diào)用通知方法
         *
         * @param mTemperature
         * @param mPressure
         * @param mHumidity
         */
        public void setData(float mTemperature, float mPressure, float mHumidity) {
            this.mTemperate = mTemperature;
            this.mPressure = mPressure;
            this.mHumidity = mHumidity;
            dataChange();
        }
    
    }
    
  • 然后就是溫度看板,獲取當(dāng)前的溫度摸柄,兩個(gè)方法颤练,一個(gè)接收數(shù)據(jù)更新,一個(gè)展示數(shù)據(jù)驱负。

    public class CurrentConditions {
    
        private float mTemperature;
        private float mPressure;
        private float mHumidity;
    
        public void update(float mTemperature, float mPressure, float mHumidity) {
            this.mTemperature = mTemperature;
            this.mPressure = mPressure;
            this.mHumidity = mHumidity;
            display();
        }
    
        public void display() {
            System.out.println("***Today mTemperature: " + mTemperature + "***");
            System.out.println("***Today mPressure: " + mPressure + "***");
            System.out.println("***Today mHumidity: " + mHumidity + "***");
        }
    }
    
  • 好了咱們開始測(cè)試一下看看效果如何

    public class InternetWeather {
    
        public static void main(String[] args) {
            CurrentConditions mCurrentConditions = new CurrentConditions();
            WeatherData mWeatherData = new WeatherData(mCurrentConditions);
            mWeatherData.setData(30, 150, 40);
        }
    }
    
    
  • 執(zhí)行結(jié)果

    ***Today mTemperature: 30.0***
    ***Today mPressure: 150.0***
    ***Today mHumidity: 40.0***
    

好了看起來需求已經(jīng)滿足了嗦玖,開始使用吧,然而沒過多久跃脊,另外一個(gè)公司也和天氣站聯(lián)系上了宇挫,想要他們提供的天氣數(shù)據(jù)來展示明天的天氣,小王一聽便說那還不簡(jiǎn)單酪术,去weather類里面加上你的看板類器瘪,加上通知翠储,加上。橡疼。

不對(duì)啊援所,這加一個(gè)使用者就這么麻煩了,那以后要是來了更多的使用者怎么辦欣除?又或者有的使用者突然又不使用這個(gè)數(shù)據(jù)了住拭,想要取消通知怎么辦?要是按這樣的話改來改去每次修改都要重新上線历帚,會(huì)非常麻煩滔岳,數(shù)據(jù)的及時(shí)性也得不到保證。小王頭疼不已抹缕,這時(shí)候公司里的老李看到了澈蟆,笑呵呵的說小王,不要著急卓研,我們?cè)僦匦驴纯茨銓懙姆椒ㄅ糠傧胂胄枰尤氲慕涌诤湍阌惺裁垂餐牡胤剑蹅儼阉殡x出去奏赘,然后再寥闪。。磨淌。疲憋。。小王在老李的幫助下很快做出了第二版實(shí)現(xiàn)

  • 首先我們加入數(shù)據(jù)提供方接口梁只,將我們之前提到了的缚柳,新增需求方,刪除需求方搪锣,以及通知加入進(jìn)去

    public interface Subject {
      /**
       * 注冊(cè)觀察者
       * @param o
       */
      void registerObserver(Observer o);
    
      /**
       * 移除觀察者
       * @param o
       */
      void removeObserver(Observer o);
    
      /**
       * 通知觀察者
       */
      void notifyObservers();
    }
    
  • 接著就是需求方的接口秋忙,主要用來獲取數(shù)據(jù)

    public interface Observer {
    
        /**
         * 數(shù)據(jù)更新
         * @param mTemperate
         * @param mPressure
         * @param mHumidity
         */
        void update(float mTemperate, float mPressure, float mHumidity);
    }
    
  • 接著修改之前的天氣數(shù)據(jù)類,在這里面我們實(shí)現(xiàn)前面的數(shù)據(jù)提供方的接口构舟,然后重寫里面的方法灰追,注冊(cè)接口我們暫時(shí)使用list作為存儲(chǔ)觀察者的集合,移除接口就是移除集合中的觀察者狗超,這里我們檢查一下觀察者是否存在集合中再移除弹澎,然后是通知接口,我們遍歷集合逐個(gè)通知需求方努咐。(這里我們是將數(shù)據(jù)一個(gè)一個(gè)的傳給使用方)苦蒿。

    public class WeatherDataSt implements Subject {
    
        private float mTemperate;
        private float mPressure;
        private float mHumidity;
        private List<Observer> mObservers;
    
        public WeatherDataSt() {
            mObservers = new ArrayList<>();
        }
    
        public float getTemperature() {
            return mTemperate;
    
        }
    
        public float getPressure() {
            return mPressure;
    
        }
    
        public float getHumidity() {
            return mHumidity;
    
        }
    
        public void dataChange() {
            notifyObservers();
        }
    
    
        public void setData(float mTemperate, float mPressure, float mHumidity) {
            this.mTemperate = mTemperate;
            this.mPressure = mPressure;
            this.mHumidity = mHumidity;
            dataChange();
        }
    
        @Override
        public void registerObserver(Observer o) {
            mObservers.add(o);
        }
    
        @Override
        public void removeObserver(Observer observer) {
            if (mObservers.contains(observer)) {
                mObservers.remove(observer);
            }
        }
    
        @Override
        public void notifyObservers() {
            for (Observer mObserver : mObservers) {
                mObserver.update(getTemperature(), getPressure(), getHumidity());
                System.out.println("開始通知:" + mObserver.getClass().getSimpleName());
            }
        }
    
    }
    
  • 天氣看板中實(shí)現(xiàn)觀察者接口,實(shí)現(xiàn)當(dāng)中的數(shù)據(jù)接口

    public class CurrentConditions implements Observer {
        private float mTemperate;
        private float mPressure;
        private float mHumidity;
    
        @Override
        public void update(float mTemperate, float mPressure, float mHumidity) {
            this.mHumidity = mHumidity;
            this.mPressure = mPressure;
            this.mTemperate = mTemperate;
            display();
        }
    
        public void display() {
            System.out.println("***Today mTemperate:" + mTemperate + "***");
            System.out.println("***Today mPressure:" + mPressure + "***");
            System.out.println("***Today mHumidity:" + mHumidity + "***");
        }
    
    }
    
  • 在查看明天的天氣面板實(shí)現(xiàn)觀察者接口

    public class ForcesConditions implements Observer {
        private float mTemperate;
        private float mPressure;
        private float mHumidity;
    
        @Override
        public void update(float mTemperate, float mPressure, float mHumidity) {
            this.mTemperate = mTemperate;
            this.mPressure = mPressure;
            this.mHumidity = mHumidity;
            display();
        }
    
        public void display() {
            System.out.println("**明天溫度:" + (mTemperate + Math.random()) + "**");
            System.out.println("**明天氣壓:" + (mPressure + 10 * Math.random()) + "**");
            System.out.println("**明天濕度:" + (mHumidity + Math.random()) + "**");
        }
    }
    
    
  • 然后我們編寫測(cè)試類

    public class InternetWeather {
    
        public static void main(String[] args) {
    
            CurrentConditions mCurrentConditions= new CurrentConditions();
            ForcesConditions mForcesConditions = new ForcesConditions();
            WeatherDataSt mWeatherDataSt = new WeatherDataSt();
    
            //注冊(cè)天氣看板
            mWeatherDataSt.registerObserver(mCurrentConditions);
            mWeatherDataSt.registerObserver(mForcesConditions);
    
            mWeatherDataSt.setData(30, 150, 40);
            //移除看板
            mWeatherDataSt.removeObserver(mCurrentConditions);
            mWeatherDataSt.setData(40, 250, 50);
        }
    
    }
    
    
  • 打印結(jié)果

    ***Today mTemperate:30.0***
    ***Today mPressure:150.0***
    ***Today mHumidity:40.0***
    開始通知:CurrentConditions
    **明天溫度:30.708855560149452**
    **明天氣壓:156.09944964615488**
    **明天濕度:40.252322896560706**
    開始通知:ForcesConditions
    **明天溫度:40.194891069978894**
    **明天氣壓:253.89775138044408**
    **明天濕度:50.95728186211783**
    開始通知:ForcesConditions
    

我們看到經(jīng)過老李的一番指導(dǎo),現(xiàn)在的設(shè)計(jì)基本上滿足了需求渗稍,即使新增看板繼續(xù)繼承觀察者接口刽肠,不需要的觀察者就可以直接移除溃肪,這樣提高了擴(kuò)展性,去除了之前代碼每次新增需要修改代碼的弊端音五,實(shí)現(xiàn)了開閉原則惫撰。

接著老李又說,小王啊躺涝,其實(shí)java內(nèi)置對(duì)象中就已經(jīng)實(shí)現(xiàn)了觀察者模式我們只需要拿來用就可以了厨钻。。坚嗜。

怎么用呢夯膀?請(qǐng)看下面的實(shí)現(xiàn)。

  • 天氣數(shù)據(jù)苍蔬,這里我們不再實(shí)現(xiàn)subject接口诱建,反而繼承java內(nèi)置對(duì)象Observable,在內(nèi)置對(duì)象中已經(jīng)幫我們實(shí)現(xiàn)了通知碟绑,注冊(cè)俺猿,移除等方法,所以我們只需要寫我們自定義的方法就行了格仲。

    public class WeatherData extends Observable {
        private float mTemperate;
        private float mPressure;
        private float mHumidity;
    
        public float getTemperature() {
            return mTemperate;
    
        }
    
        public float getPressure() {
            return mPressure;
    
        }
    
        public float getHumidity() {
            return mHumidity;
    
        }
    
        /**
         * 通知數(shù)據(jù)已修改
         */
        public void dataChange() {
            //Observable的源碼中setChanged()押袍,設(shè)置changed為true表示數(shù)據(jù)已經(jīng)修改,只有當(dāng)changed為true的時(shí)候notifyObservers()方法才會(huì)通知修改
            this.setChanged();
            this.notifyObservers(new AssembleWeatherData(getTemperature(), getPressure(), getHumidity()));
    
        }
    
        /**
         * 修改數(shù)據(jù)
         * @param mTemperate
         * @param mPressure
         * @param mHumidity
         */
        public void setData(float mTemperate, float mPressure, float mHumidity) {
            this.mTemperate = mTemperate;
            this.mPressure = mPressure;
            this.mHumidity = mHumidity;
            dataChange();
        }
    
    }
    
  • 同樣天氣看板類只用實(shí)現(xiàn)Observer接口凯肋,重寫當(dāng)中的update方法就行了

    public class CurrentConditions implements Observer {
    
        private float mTemperate;
        private float mPressure;
        private float mHumidity;
    
        @Override
        public void update(Observable arg0, Object arg1) {
            this.mTemperate = ((AssembleWeatherData) (arg1)).mTemperate;
            this.mPressure = ((AssembleWeatherData) (arg1)).mPressure;
            this.mHumidity = ((AssembleWeatherData) (arg1)).mHumidity;
            display();
        }
    
        public void display() {
            System.out.println("***Today mTemperate:" + mTemperate + "***");
            System.out.println("***Today mPressure:" + mPressure + "***");
            System.out.println("***Today mHumidity:" + mHumidity + "***");
        }
    }
    
  • 另外一個(gè)天氣看板谊惭,一樣的實(shí)現(xiàn)

    public class ForecastConditions implements Observer {
    
        private float mTemperate;
        private float mPressure;
        private float mHumidity;
    
        /**
         * 數(shù)據(jù)更新 同時(shí)調(diào)用展示數(shù)據(jù)方法
         *
         * @param arg0
         * @param arg1
         */
        @Override
        public void update(Observable arg0, Object arg1) {
            this.mTemperate = ((AssembleWeatherData) (arg1)).mTemperate;
            this.mPressure = ((AssembleWeatherData) (arg1)).mPressure;
            this.mHumidity = ((AssembleWeatherData) (arg1)).mHumidity;
            display();
        }
    
        /**
         * 展示數(shù)據(jù)
         */
        public void display() {
            System.out.println("**明天溫度:" + (mTemperate + Math.random()) + "**");
            System.out.println("**明天氣壓:" + (mPressure + 10 * Math.random()) + "**");
            System.out.println("**明天濕度:" + (mHumidity + Math.random()) + "**");
        }
    
    
    }
    
  • 為了方便傳遞數(shù)據(jù),我們?cè)傩陆ㄒ粋€(gè)AssembleWeatherData類侮东,用于設(shè)計(jì)返回?cái)?shù)據(jù)內(nèi)容

    public class AssembleWeatherData {
        public float mTemperate;
        public float mPressure;
        public float mHumidity;
    
        public AssembleWeatherData(float mTemperate, float mPressure, float mHumidity) {
            this.mTemperate = mTemperate;
            this.mPressure = mPressure;
            this.mHumidity = mHumidity;
        }
    }
    
  • 接著我們新建測(cè)試類

    public class InternetWeather {
        
        public static void main(String[] args) {
            CurrentConditions mCurrentConditions;
            ForecastConditions mForecastConditions;
            WeatherData mWeatherData;
    
            mCurrentConditions = new CurrentConditions();
            mForecastConditions = new ForecastConditions();
            mWeatherData = new WeatherData();
    
            //java內(nèi)置觀察者模式中的addObserver方法圈盔,用于注冊(cè)觀察者
            mWeatherData.addObserver(mCurrentConditions);
            mWeatherData.addObserver(mForecastConditions);
            mWeatherData.setData(30, 150, 40);
    
            //內(nèi)置方法,用于移除觀察者悄雅,重新修改數(shù)據(jù)
            mWeatherData.deleteObserver(mCurrentConditions);
            mWeatherData.setData(35, 150, 60);
    
        }
    }
    
  • 打印數(shù)據(jù)

    **明天溫度:30.676433372455936**
    **明天氣壓:154.2686999140749**
    **明天濕度:40.015042461544155**
    ***Today mTemperate:30.0***
    ***Today mPressure:150.0***
    ***Today mHumidity:40.0***
    **明天溫度:35.4090594721898**
    **明天氣壓:157.28459919820716**
    **明天濕度:60.286781430877525**
    

果然經(jīng)過老李一番重構(gòu)驱敲,小王很快就明白了,觀察者模式的好處煤伟,看著老李地中海的腦袋心想,這或許就是成為大牛的代價(jià)吧木缝。便锨。。我碟。

總結(jié)

前面講述了兩種觀察者模式的實(shí)現(xiàn)方法放案,一個(gè)是我們自己寫的接口做的實(shí)現(xiàn),另一種是直接使用java中的內(nèi)置觀察者模式實(shí)現(xiàn)矫俺,兩者各有各的優(yōu)勢(shì)和缺點(diǎn)吱殉,對(duì)于一般的情況內(nèi)置觀察者就能滿足掸冤,但是有一點(diǎn)Observable是一個(gè)類不是接口所以只能繼承他,但由于java只能對(duì)接口多繼承友雳,所以使用內(nèi)置對(duì)象的時(shí)候需要多繼承的時(shí)候就不能使用了稿湿,自定義的方法可以更靈活,但是需要寫更多的代碼押赊,魚和熊掌不可兼得饺藤。

鏈接

本文代碼倉(cāng)庫(kù)地址:https://gitee.com/singlekingdom/JavaDesignPatterns.git

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市流礁,隨后出現(xiàn)的幾起案子涕俗,更是在濱河造成了極大的恐慌,老刑警劉巖神帅,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件再姑,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡找御,警方通過查閱死者的電腦和手機(jī)元镀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萎坷,“玉大人凹联,你說我怎么就攤上這事《叩担” “怎么了蔽挠?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瓜浸。 經(jīng)常有香客問我澳淑,道長(zhǎng),這世上最難降的妖魔是什么插佛? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任杠巡,我火速辦了婚禮,結(jié)果婚禮上雇寇,老公的妹妹穿的比我還像新娘氢拥。我一直安慰自己,他們只是感情好锨侯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布嫩海。 她就那樣靜靜地躺著,像睡著了一般囚痴。 火紅的嫁衣襯著肌膚如雪叁怪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天深滚,我揣著相機(jī)與錄音奕谭,去河邊找鬼涣觉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛血柳,可吹牛的內(nèi)容都是我干的官册。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼混驰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼攀隔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起栖榨,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤昆汹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后婴栽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體满粗,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年愚争,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了映皆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡轰枝,死狀恐怖捅彻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞍陨,我是刑警寧澤步淹,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站诚撵,受9級(jí)特大地震影響缭裆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寿烟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一澈驼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧筛武,春花似錦缝其、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至硕噩,卻和暖如春假残,著一層夾襖步出監(jiān)牢的瞬間缭贡,已是汗流浹背炉擅。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工辉懒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人谍失。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓眶俩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親快鱼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子颠印,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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