Java設計模式(6)之觀察者模式學習總結

boker_han發表於2017-10-28
    觀察者模式:在觀察者模式中,存在著物件之間的一對多的依賴關係,即一個物件的狀態發生改變時,所有依賴於該物件的物件都會得到通知,並對自身的狀態進行更新;
    觀察者模式的學習中,物件之間的一對多的依賴關係是學習觀察者模式的切入點,而被依賴物件(目標物件)的狀態改變會對依賴物件(觀察者物件)狀態產生影響是觀察者模式的關鍵所在;只有物件之間形成一對多的依賴關係,才能實現被依賴物件與依賴物件之間的狀態更新的互動作用;
    生活中,其實存在很多觀察者模式的應用場景,比如天氣預報簡訊通知,當你在手機上開通了簡訊通知天氣服務之後,每天你都會收到當天的天氣資訊的簡訊通知;再比如,你的手機上安裝了騰訊體育APP,那麼你只要接受該APP提供的推送即時訊息通知的服務,那麼一旦有更新的體育資訊,將會向你的手機推送相關資訊;
    下面通過一個生活中的場景,進行相關的觀察者模式設計程式碼:
    package com.pattern.observer;
    import java.util.ArrayList;
    import java.util.List;
    /**
     * 抽象目標類
     * @author Administrator
     */
    public  class Subject {
        //訂閱者列表,用於存放訂閱者物件
        private List<Observer> list = new ArrayList<Observer>();
        //新增訂閱者
        public void add(Observer o) 
        {
            list.add(o);
        }
        //刪除指定訂閱者
        public void delete(Observer o)
        {
            list.remove(o);
        }
        //通知所有訂閱者
        public void notifyAllObservers() 
        {
            for (Observer observer : list)
             {
                //將目標物件傳入觀察者物件的update方法
                observer.update(this);
            }
        }
    }


    package com.pattern.observer;
    /**
     * 具體目標類
     * @author Administrator
     */
    public class WeatherSubject extends Subject {
        //目標物件的狀態資訊
        private String information;
        public String getInformation() 
        {
            return information;
        }
        //設定或更新目標狀態資訊
        public void setInformation(String information)
         {
            this.information = information;
            //更新狀態資訊後,呼叫通知方法,通知所有訂閱者,使其進行各自的狀態更新
            this.notifyAllObservers();
        }   
    }


    package com.pattern.observer;
    /**
     * 觀察者介面
     * @author Administrator
     */
    public interface Observer {
        //更新觀察者狀態
        public abstract void update(Subject subject);
    }


    package com.pattern.observer;
    /**
     * 具體觀察者類
     * @author Administrator
     */
    public class ConcreteObserver implements Observer {
        //觀察者的名字
        private String observerName;
        //觀察者的待更新狀態資訊
        private String information;
        //接收到資訊後的響應資訊
        private String hint;

        public ConcreteObserver(String name,String hint)
         {
            observerName = name;
            this.hint = hint;
        }

        @Override
        public void update(Subject subject) 
            information =((WeatherSubject)subject).getInformation();
            System.out.println(observerName+"收到了"+information+"的通知"+","+hint);
        }

    }


    package com.pattern.observer;
    //測試類
    public class TestObserverPattern {

        public static void main(String[] args) {
            //1.建立目標物件
            WeatherSubject subject = new WeatherSubject(); 
            //2.建立觀察者物件
            ConcreteObserver observerOne = new ConcreteObserver("Tom", "已收到,考慮是否去購物");
            ConcreteObserver observerTwo = new ConcreteObserver("Jake", "已收到,考慮是否去旅遊");
            //3.新增觀察者
            subject.add(observerOne);
            subject.add(observerTwo);
            //4.更新目標狀態資訊
            subject.setInformation("天氣晴朗");
        }
    }
    在設計觀察者模式的時候,由於對於向觀察者物件傳送的訊息內容的不同,觀察者模式又分為推模型和拉模型;

    拉模型:將目標物件傳給觀察者物件,觀察者物件需要目標物件的哪一部分的狀態資訊,就通過該目標物件獲取資訊;當觀察者物件對更新資訊的需求發生變化時,拉模型可以很好地應對變化的場景;在上述的例子中,採用的是拉模型進行觀察者模型的設計; 

    推模型:將指定的更新資訊通知給觀察者物件,當觀察者對更新資訊有了不同的要求之後,這種方式將會顯得難以適應變化的要求;

    如果想將上述例子通過推模型實現,只需要對具體觀察者類中的update進行修改和對具體目標類中的notifyAllObservers進行重寫;修改如下: 

    public void notifyAllObservers() 
    {
        for (Observer observer : list)
         {
            //將傳入觀察者物件的update方法
            observer.update(information);
        }
    }

    public void update(String information) 
    {
        this.information = information;
        System.out.println(observerName+"收到了"+information+"的通知"+","+hint);
    }

    在Java中,其實提供了觀察者模式的實現,利用java.util包下的Observable類和Observer介面就可以大大簡化我們自己設計觀察者模式的步驟;利用Java提供的觀察者模式的實現將上述例子重新進行設計:
    /**
     *目標類的設計
     */
    package com.pattern.observerInJava;
    import java.util.Observable;

    public class ConcreteSubject extends Observable  
    {
        private String weatherInfor;//天氣資訊
        public String getWeatherInfor() 
        {
            return weatherInfor;
        }
        public void setWeatherInfor(String weatherInfor)  
        {
            this.weatherInfor = weatherInfor;
            //使用Java提供的觀察者模式,在進行通知觀察者更新資訊之前所必需的一步
            this.setChanged();
            //設定拉模型,將目標物件直接傳到觀察者方法中
            this.notifyObservers();
            //設定推模型,將具體需要通知的資訊傳到觀察著方法中
            this.notifyObservers(weatherInfor);
        }   
    }


    /**
     *觀察者類的設計
     */
    package com.pattern.observerInJava;
    import java.util.Observable;
    import java.util.Observer;

    public class ConcreteObserver1 implements Observer 
    {
        private String observerName;
        private String weatherInfor;

        public ConcreteObserver1(String name) 
        {
            observerName = name;
        }
        @Override
        public void update(Observable o, Object arg)  
        {
            //採用拉模型進行觀察者訊息的更新
            weatherInfor = ((ConcreteSubject)o).getWeatherInfor();
            System.out.println(observerName+"從目標處拉取了更新的資訊");
            System.out.println(observerName+"收到了"+weatherInfor);
            //採用推模型進行觀察者訊息的更新
            System.out.println(observerName+"接收到了推動的更新的資訊");
            System.out.println(observerName+"收到了"+weatherInfor);
        }
    }


    /**
     *測試類的設計
     */
    package com.pattern.observerInJava;
    import com.pattern.observerInJava.ConcreteObserver1;
    public class TestObserverInJava
    {
        public static void main(String[] args)  
        {
            //1.建立目標物件
            ConcreteSubject subject = new ConcreteSubject(); 
            //2.建立觀察者物件
            ConcreteObserver1 observerOne = new ConcreteObserver1("Tom");
            ConcreteObserver1 observerTwo = new ConcreteObserver1("Jake");
            //3.新增觀察者
            subject.addObserver(observerOne);
            subject.addObserver(observerTwo);
            //4.更新目標狀態資訊
            subject.setWeatherInfor("天氣晴朗");
        }
    }
    進一步地提出問題,當不同的觀察者物件對目標物件的更新資訊中感興趣的方面不同,比如觀察者物件A只對下雨天和陰天感興趣,觀察者物件B只對下雨天感興趣,這時,將需要根據不同的需求向不同的觀察者物件傳送相應的更新資訊;
    這裡可以使用面向介面程式設計的思想進行解決,設計一個抽象目標類,在該類中實現其具體實現類中公有的部分,而將notifyAllObservers方法放在其具體實現類中進行實現,在該方法中根據需求的不同進行不同的處理;
    notifyAllObservers方法的具體實現可參考如下程式碼:
    public void notifyAllObservers() 
    {
        for (Observer observer : list)
         {
             //如果天氣為下雨天,則只向觀察者A、B推送更新資訊
            if("下雨天".equals(weatherInfor))
            {
                if(observer.getObserverName.equals("A"))
                {
                    observer.update(this);
                }
                if(observer.getObserverName.equals("B"))
                {
                    observer.update(this);
                }
            }
            //如果天氣為陰天,則指向觀察者A推送更新資訊
            if("陰天".equals(weatherInfor))
            {
                if(observer.getObserverName.equals("A"))
                {
                    observer.update(this);
                }
            }
        }
    }


    在當自己進行觀察者模式設計的時候,這裡給出一些適用於觀察者模式設計的注意點:
        ①命名上的建議:
            目標介面定義為Subject
            觀察者介面定義為Observer
            觀察者介面中的的更新方法定義為update
        ②觸發通知的時機應該在更新完目標物件的狀態資訊之後通知
        ③通知的順序:觀察者之間是平行的,在觀察者模式中不需要指定通知的順序 
    觀察者模式設計流程分析:
        準備階段 1、建立目標物件 2、建立觀察者物件 3、在目標物件中註冊觀察者物件
        執行階段 1、改變目標物件的狀態 2、通知所有已註冊觀察者物件進行相應的處理 3、回撥目標物件,獲取相應資料

    觀察者優點及適用場景:
    優點:
    ①觀察者模式實現了觀察者和目標之間的抽象耦合;
    ②觀察者模式實現了動態聯動;
    ③觀察者模式支援廣播通訊;
    使用場景:
    ①在一個業務場景中,某一方面的操作需要依賴另一方面狀態的改變;
    ②在更改一個物件的狀態同時,需要連帶著修改其他物件的狀態,並且需要被連帶修改的物件的個數不知道;
    ③當需要實現通知物件和被通知物件之間的鬆散耦合的時候,選用觀察者模式;

相關文章