設計模式-觀察者(一)

設計模式-觀察者(一)

大家好,我們又見面了,還記得上次所寫的 <<設計模式-策略模式>>麼?,我們今天來學習另一個模式之前,先複習一下上次的”策略模式”是怎麼定義的吧:

定義:

策略模式定義:算法族,分別封裝起來,讓他們之間可以互相替換,次模式讓算法的變化獨立於使用算法的客戶.

“策略模式”中我們還學到了兩個設計原則:

  • 1 :找出應用中可能需要變化之處,把他們獨立起來,不要和那些不需要變化的部分的代碼混合在一起.
  • 2 :針對藉口編程,而不是針對實現編程.

思考……………

經過對”策略模式”的思考,是不是有點印象了? 下面我們繼續向下一個模式進發把……..

這次我們先推出這個模式,名字爲”觀察者模式”, 它的定義: 觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新.

不懂?

當然,如果能現在就能明白其真諦的話,你可以關瀏覽器了,以後都不需要再來GC博客了……(開玩笑 ^_^)


我們一步一步來,先了解一下什麼是觀察者模式.

*不知道你訂沒訂閱報紙(如果你從小到大沒訂閱過報紙或者書刊雜誌,我只能說你錯過了一個生活體驗..) 好吧,瞎扯了,我們先介紹一下訂閱報刊的過程:
假如你家附近有一所報社,而你又是<<意林>>(高中生應該都讀過的)的愛好者,你當然會迫不及待的去訂閱一年屬於自己的<<意林>>.當你去報社訂閱了<<意林>>之後,每當報社收到新出版的<<意林>>,它就會將你的那份送到你的家中供你閱讀.*

好吧.整個流程應該弄明白了吧,是在不明白可以親自去訂閱一年的<<意林>>體驗一把哈.

到這裏,可能你會想:訂閱報刊與”觀察者模式”有什麼關係啊~

我只能回答你:其實訂閱報刊整個流程就是"觀察者模式"的整個流程,也就是說"訂閱報刊"用了"觀察者模式".
當然,如果你已經想到了,那麼只能說你的智商真的是太高了(@_@)

報社就相當於”觀察者模式”裏的”發佈者(publish)/主題(Subject)”,而你就相當於”觀察者模式”裏的”訂閱者(Subscribe)/觀察者(Observer)”

“觀察者模式”介紹完畢~~~


騙你的,代碼還沒寫,怎麼可能介紹完畢呢.況且”觀察者模式”有好多種,今天只介紹其一….

現在我們開始寫代碼吧…用代碼來實現上面的”訂閱報刊”的功能…

//定義報社類
public class NewspaperOffice  {

    //定義主題後期會更新的數據
    private int updatedata;

    //通知所有的<<意林>>訂閱者
    @Override
    public void notifyObservers() {
           //訂閱者1訂閱了意林
            Subscriber1 s1 = new Subscriber1();
            //讓訂閱者1更新數據,也就是給訂閱者1送去<<意林>>
            s1.update(this.updatedata);

            //訂閱者2訂閱了意林
            Subscriber1 s2 = new Subscriber2();
            //讓訂閱者2更新數據,也就是給訂閱者2送去<<意林>>
            s2.update(this.updatedata);
   }

    //當主題裏的數據進行更新的時候,通知所有的觀察者
    public void setUpdatedata(int updatedata) {
            this.updatedata = updatedata;
            notifyObservers();
    }

}


//定義訂閱者1類
public class Subscriber1 {

    //定義訂閱者自己的信息
    private String name;

    //定義訂閱者需要在報社那裏接到的數據
    private int updatedata;

    //構造器裏還裝入了該訂閱者的名字
    public Subscriber1(String name){
                this.name = name;
            }

    public void display() {
                System.out.println("我是訂閱者,我的名字叫: "+name+" , 這是從報社接到的數據: "+ updatedata);
            }

    public void update(int updatedata) {
                this.updatedata = updatedata;
                display();
            }
}

//定義訂閱者2類
public class Subscriber2 {

    //定義訂閱者自己的信息
    private String name;
    private int age;

    //定義訂閱者需要在報社那裏接到的數據
    private int updatedata;

    //構造器裏還裝入了該訂閱者的名字
    public Subscriber1(String name, int age){
                this.name = name;
            }

    //顯示出信息
    public void display() {
                System.out.println("我是訂閱者,我的名字叫: "+name+",  我的年齡是: "+age+" , 這是從報社接到的數據: "+ updatedata);
            }

    //更新訂閱者手中的信息(也就是更新手中的<<意林>>書的個數)
    public void update(int updatedata) {
                this.updatedata = updatedata;
                display();
            }

}

看了上面的代碼,你有沒有一種想罵我的感覺,上面的代碼太不靈活了,當然這不可能是”觀察者模式”哈,我只是寫了一個java初學者會設計的模式.

上面代碼很不靈活,的確.如果有一天,又有一個”訂閱者3”去報刊訂閱<<意林>>,NewspaperOffice這個類就需要重新寫代碼,在notifyObservers()方法裏添加”訂閱者3”的實例…當有一天”訂閱者2”不想訂閱了,NewspaperOffice類又要重新重構代碼.麻煩至極啊…

很顯然啊,上面的代碼太別沒有遵循之間我們所說的兩個設計原則,所有是一組極差,及其不具備靈活性的代碼…

下面,我們用”觀察者模式”編寫上面的代碼…


首先,我們需要面向接口編程,把NewspaperOfficeSubscriber1,Subscriber2提取一下.

/*
這裏提供了一個Subject(主題)接口,接口裏定義了:註冊觀察者方法,刪除觀察者方法,向已經註冊的觀察者發送消息的方法
*/

public interface Subject {
    //用來註冊觀察者
    public void registerObserver(Observer o);
    //用來刪除觀察者
    public void removeObserver(Observer o);
    //通知所有已經註冊的觀察者
    public void notifyObservers();
}

/*
定義Observer(觀察者)接口,所有觀察者都具備更新內部數據的功能
*/
public interface Observer {
        //更新觀察者內部的數據
        public void update(int updatedata);
}

/*
定義Subscriber(訂閱者)接口,所有訂閱者都可以展出自己的內部信息(也就是所有訂閱者都可以向別人展出手中意林數目
*/
public interface Subscriber {
        //每一個訂閱者都可以顯示自己的信息
        public void display();
}


/*
下面,我們的報社出廠了
*/
import java.util.ArrayList;
import java.util.List;

//報社對象實線了主題藉口
public class NewspaperOffice implements Subject {

        //定義主題後期會更新的數據
        private int updatedata;

        //建立一個list來裝註冊進來的觀察者
        private List <Observer>observers;


        //構造函數,構造list
        public NewspaperOffice(){
            observers = new ArrayList<Observer>();
        }
        //添加註冊觀察者
        @Override
        public void registerObserver(Observer o) {
            observers.add(o);
        }

        //移除觀察者
        @Override
        public void removeObserver(Observer o) {
            int i = observers.indexOf(o);
            if(i >= 0){
                observers.remove(i);
            }
        }

        //通知所有的觀察者
        @Override
        public void notifyObservers() {
            for(Observer observer: observers){
                observer.update(this.updatedata);
            }
        }

        //當主題裏的數據進行更新的時候,通知所有的觀察者
        public void setUpdatedata(int updatedata) {
            this.updatedata = updatedata;
            notifyObservers();
        }

}





/*
定義訂閱者1 實現了觀察者接口,訂閱者的接口

*/
public class Subscriber1 implements Observer, Subscriber {

    //定義訂閱者自己的信息
    private String name;

    //定義訂閱者需要在報社那裏接到的數據
    private int updatedata;

    //定義觀察者引用
    private Subject subject;

    //構造器裏接受到該觀察者需要註冊的到哪個主題。並且註冊到該主題上
    //構造器裏還裝入了該訂閱者的名字
    public Subscriber1(Subject subject, String name){
        this.name = name;
        this.subject = subject;
        subject.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("我是訂閱者,我的名字叫: "+name+" , 這是從報社接到的數據: "+ updatedata);
    }

    @Override
    public void update(int updatedata) {
        this.updatedata = updatedata;
        display();
    }

}


/*
定義訂閱者2,基本內容與訂閱者1相似,但是請注意,訂閱者1與訂閱者2是完全不同的兩個類型,因爲訂閱者2裏需要填寫年齡信息.
如果兩個類一模一樣,那寫成一個類不就好了麼?
爲什麼要這樣設計兩個類呢? 因爲我們後期的訂閱者是各種各樣的,訂閱者怎麼變化都無所謂,這充分說明了我們類與類之間的耦合程度非常低.
*/
public class Subscriber2 implements Observer, Subscriber {

        //定義訂閱者自己的信息
        private String name;
        private int age;

        //定義訂閱者需要在報社那裏接到的數據
        private int updatedata;

        //定義觀察者引用
        private Subject subject;

        //構造器裏接受到該觀察者需要註冊的到哪個主題。並且註冊到該主題上
        //構造器裏還裝入了該訂閱者的名字
        public Subscriber2(Subject subject, String name, int age){
            this.age = age;
            this.name = name;
            this.subject = subject;
            subject.registerObserver(this);
        }

        @Override
        public void display() {
            System.out.println("我是訂閱者,我的名字叫: "+name+",  我的年齡是: "+age+" , 這是從報社接到的數據: "+ updatedata);
        }

        @Override
        public void update(int updatedata) {
            this.updatedata = updatedata;
            display();
        }

}

基本結構都已經設計完了,現在我來寫個測試類進行測試

public class Main {
        public static void main(String[] args) {
            //創建報社
            NewspaperOffice nsp1 = new NewspaperOffice();

            //用Subscriber1創建訂閱者1,並且將他註冊到報社
            Subscriber1 GC = new Subscriber1(nsp1, "GC");
            //用Subscriber2創建訂閱者2,也註冊到報社
            Subscriber2 YP = new Subscriber2(nsp1, "YP", 33);

            System.out.println("===========下面開始發佈第1版報紙,此時GC與YP都在報社裏========");
            //現在報社開始發佈第一版報紙
            nsp1.setUpdatedata(1);
            System.out.println("===========下面開始發佈第2版報紙,此時GC與YP都在報社裏========");
            //報社發佈第二版報紙
            nsp1.setUpdatedata(2);

            //現在GC不訂閱報紙了,報社刪除GC
            nsp1.removeObserver(GC);
            System.out.println("===========下面開始發佈第3版報紙,此時只有YP都在報社裏========");
            //刪除GC之後,報社又發佈了第三版報紙
            nsp1.setUpdatedata(3);

          /*
             * 運行結果如下:
             * ===========下面開始發佈第1版報紙,此時GC與YP都在報社裏========
             * 我是訂閱者,我的名字叫: GC , 這是從報社接到的數據: 1
             * 我是訂閱者,我的名字叫: YP,  我的年齡是: 33 , 這是從報社接到的數據: 1
             * ===========下面開始發佈第2版報紙,此時GC與YP都在報社裏========
             * 我是訂閱者,我的名字叫: GC , 這是從報社接到的數據: 2
             * 我是訂閱者,我的名字叫: YP,  我的年齡是: 33 , 這是從報社接到的數據: 2
             * ===========下面開始發佈第3版報紙,此時只有YP都在報社裏========
             * 我是訂閱者,我的名字叫: YP,  我的年齡是: 33 , 這是從報社接到的數據: 3
             */

        }
}

這是“觀察者”模式的類圖,裏面只有Subject類與Observer類之間的關係,請仔細思考。
觀察者模式


好吧,我承認,”觀察者模式”不是那麼容易理解,如果你看了我上面所寫的內容之後就的能理解”觀察者模式”,我真的再次讚歎一下:你的智商真的很高很高….

經過對將近兩個小時的奮鬥,終於把<<設計模式-觀察者模式>>寫完了,如果有不會的,沒看懂的請Email或者留言給我……………

請轉發OR複製的同學,標註出處,尊重作者勞動成果,謝謝親


謝謝觀看,結束本節!

注:本文借鑑了《HeadFirst-設計模式》一書,作者總結的內容不到書內容精彩十分之一,還望大家多讀原著。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章