設(shè)計模式--觀察者模式

本文的結(jié)構(gòu)如下:

  • 什么是觀察者模式
  • 為什么要用該模式
  • 模式的結(jié)構(gòu)
  • 代碼示例
  • 推模型和拉模型
  • 優(yōu)點和缺點
  • 適用環(huán)境
  • 模式應(yīng)用
  • 總結(jié)

一、什么是觀察者模式

觀察者模式定義了對象之間的一對多依賴,這樣一來,當(dāng)一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并自動更新。觀察者模式又叫做發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式。

上面是比較官方的定義,可以用訂閱雜志的模式來通俗理解觀察者模式。

如上圖,現(xiàn)有一本文學(xué)雜志,一一、二二、三三都喜歡文學(xué),所以花錢訂閱了該雜志,四四不喜歡文學(xué),在其他三人訂閱雜志的時候,他選擇躲在窩里睡大覺。

新的一月到來,雜志發(fā)布了最新的一期,這天一大早,雜志社就把雜志分別送到了一一、二二、三三手中,三人坐著椅子上,一邊喝著熱牛奶,一邊翻看著絕世好文章,四四只能落寞躲在角落上,偷偷打起農(nóng)藥。

這就是觀察者模式的通俗解釋,一個“主題”,有一個或多個“觀察者”,“觀察者”訂閱“主題”,“主題”更新,訂閱了該“主題”的“觀察者”都能收到通知,并進(jìn)行相應(yīng)的行為。

二、為什么要用該模式

軟件系統(tǒng)常常要求建立一種對象與對象之間的依賴關(guān)系,一個對象發(fā)生改變時將自動通知其他對象,其他對象將相應(yīng)做出反應(yīng)。做到這一點的設(shè)計方案有很多,但是為了使系統(tǒng)能夠易于復(fù)用,應(yīng)該選擇低耦合度的設(shè)計方案。減少對象之間的耦合有利于系統(tǒng)的復(fù)用,但是同時需要使這些低耦合度的對象之間能夠維持行動的協(xié)調(diào)一致,保證高度的協(xié)作。觀察者模式是滿足這一要求的各種設(shè)計方案中最重要的一種。

三、模式的結(jié)構(gòu)

這是摘自《HeadFirst 設(shè)計模式》一書中的類圖。從圖中應(yīng)該能很清楚看出觀察者模式涉及的角色:

  • 抽象主題(Subject)角色:抽象主題角色把所有對觀察者對象的引用保存在一個聚集(比如ArrayList對象)里,每個主題都可以有任何數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
  • 具體主題(ConcreteSubject)角色:將有關(guān)狀態(tài)存入具體觀察者對象;在具體主題的內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
  • 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己,這個接口叫做更新接口。
  • 具體觀察者(ConcreteObserver)角色:存儲與主題的狀態(tài)自恰的狀態(tài)。具體觀察者角色實現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。如果需要,具體觀察者角色可以保持一個指向具體主題對象的引用。

四、代碼示例

用代碼實現(xiàn)“訂閱報紙”,觀察者模式運用如下:

1.首先有抽象主題

/**
 * 抽象主題
 *
 * Created by w1992wishes on 2017/10/17.
 */
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

2.有實現(xiàn)抽象主題的具體主題

/**
 * 具體主題
 *
 * Created by w1992wishes on 2017/10/17.
 */
public class Maganize implements Subject {

    private List<Observer> observers;
    private String flag;

    public Maganize(){
        observers = new ArrayList<Observer>();
    }

    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    public void removeObserver(Observer observer) {
        int i = observers.indexOf(observer);
        if (i > 0){
            observers.remove(i);
        }
    }

    public void notifyObservers() {
        for (int i=0; i<observers.size(); i++){
            Observer observer = (Observer)observers.get(i);
            observer.update(flag);
        }
    }

    public void publishNewMaganize(String flag){
        this.flag = flag;
        System.out.println("publish new maganize: " + flag);
        notifyObservers();
    }
}

3.還有抽象觀察者

/**
 * 抽象觀察者
 *
 * Created by w1992wishes on 2017/10/17.
 */
public interface Observer {
    void update(String flag);
}

4.還有實現(xiàn)抽象觀察者的三個具體觀察者

/**
 * 具體觀察者一一
 * Created by w1992wishes on 2017/10/17.
 */
public class Yiyi implements Observer {

    private String flag;

    public void update(String flag) {
        this.flag = flag;
        System.out.println("I am yiyi, now reading maganize: " + flag);
    }
}
/**
 * 具體觀察者--二二
 *
 * Created by w1992wishes on 2017/10/17.
 */
public class Erer implements  Observer {

    private String flag;

    public void update(String flag) {
        this.flag = flag;
        System.out.println("I am erer, now reading maganize: " + flag);
    }
}
/**
 * 具體觀察者三三
 *
 * Created by w1992wishes on 2017/10/17.
 */
public class Sansan implements Observer {

    private String flag;

    public void update(String flag) {
        this.flag = flag;
        System.out.println("I am sansan, now reading maganize: " + flag);
    }
}

5.最后有客戶端

/**
 * 客戶端
 *
 * Created by w1992wishes on 2017/10/17.
 */
public class Client {
    public static void main(String[] args) {
        //創(chuàng)建主題對象
        Maganize maganize = new Maganize();

        //創(chuàng)建觀察者
        Observer yiyi = new Yiyi();
        Observer erer = new Erer();
        Observer sansan = new Sansan();

        //將觀察者對象登記到主題對象上
        maganize.registerObserver(yiyi);
        maganize.registerObserver(erer);
        maganize.registerObserver(sansan);

        //改變主題對象的狀態(tài)(發(fā)布新雜志)
        maganize.publishNewMaganize("October New");
    }
}

顯示結(jié)果如下:
publish new maganize: October New
I am yiyi, now reading maganize: October New
I am erer, now reading maganize: October New
I am sansan, now reading maganize: October New

在運行時,這個客戶端首先創(chuàng)建了具體主題類的實例,以及三個觀察者對象。然后,它調(diào)用主題對象的registerObserver()方法,將觀察者對象向主題對象登記,也就是將它加入到主題對象的聚集中去。

這時,客戶端調(diào)用主題的publishNewMaganize()方法,改變了主題對象的內(nèi)部狀態(tài)。主題對象在狀態(tài)發(fā)生變化時,調(diào)用超類的notifyObservers()方法,通知所有登記過的觀察者對象。

五、推模型和拉模型

6.1、推模型和拉模型

在觀察者模式中,又分為推模型和拉模型兩種方式。

  • 推模型

主題對象向觀察者推送主題的詳細(xì)信息,不管觀察者是否需要,推送的信息通常是主題對象的全部或部分?jǐn)?shù)據(jù)。

  • 拉模型

主題對象在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到主題對象中獲取,相當(dāng)于是觀察者從主題對象中拉數(shù)據(jù)。一般這種模型的實現(xiàn)中,會把主題對象自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取數(shù)據(jù)的時候,就可以通過這個引用來獲取了。

根據(jù)上面的描述,發(fā)現(xiàn)前面的例子就是典型的推模型,下面給出一個拉模型的實例。

6.2、拉模型

1.拉模型通常都是把主題對象當(dāng)做參數(shù)傳遞。

/**
 * 拉模型 抽象觀察者
 *
 * Created by w1992wishes on 2017/10/17.
 */
public interface Observer {
    /**
     * update 方法
     *
     * @param subject  傳入主題對象,方面獲取相應(yīng)的主題對象的狀態(tài)
     */
    void update(Subject subject);
}

2.拉模型的具體觀察者類

/**
 * 拉模型 具體觀察者一一
 * Created by w1992wishes on 2017/10/17.
 */
public class Yiyi implements Observer {

    private String flag;

    public void update(Subject subject) {
        this.flag = ((Maganize) subject).getFlag();
        System.out.println("I am yiyi, now reading maganize: " + flag);
    }
}
/**
 * 拉模型 具體觀察者--二二
 *
 * Created by w1992wishes on 2017/10/17.
 */
public class Erer implements  Observer {

    private String flag;

    public void update(Subject subject) {
        this.flag = ((Maganize) subject).getFlag();
        System.out.println("I am erer, now reading maganize: " + flag);
    }
}
/**
 * 拉模型 具體觀察者三三
 *
 * Created by w1992wishes on 2017/10/17.
 */
public class Sansan implements Observer {

    private String flag;

    public void update(Subject subject) {
        this.flag = ((Maganize) subject).getFlag();
        System.out.println("I am sansan, now reading maganize: " + flag);
    }
}

3.拉模型 拉模型的具體主題類

拉模型的主題類主要的改變是nodifyObservers()方法。在循環(huán)通知觀察者的時候,也就是循環(huán)調(diào)用觀察者的update()方法的時候,傳入的參數(shù)不同了。

/**
 * 具體主題
 *
 * Created by w1992wishes on 2017/10/17.
 */
public class Maganize implements Subject {

    private List<Observer> observers;
    private String flag;

    public Maganize(){
        observers = new ArrayList<Observer>();
    }

    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    public void removeObserver(Observer observer) {
        int i = observers.indexOf(observer);
        if (i > 0){
            observers.remove(i);
        }
    }

    public void notifyObservers() {
        for (int i=0; i<observers.size(); i++){
            Observer observer = (Observer)observers.get(i);
            observer.update(this);
        }
    }

    public void publishNewMaganize(String flag){
        this.flag = flag;
        System.out.println("publish new maganize: " + flag);
        notifyObservers();
    }

    public String getFlag() {
        return flag;
    }
}

6.3、兩種模式的比較

  • 推模型是假定主題對象知道觀察者需要的數(shù)據(jù);而拉模型是主題對象不知道觀察者具體需要什么數(shù)據(jù),沒有辦法的情況下,干脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
  • 推模型可能會使得觀察者對象難以復(fù)用,因為觀察者的update()方法是按需要定義的參數(shù),可能無法兼顧沒有考慮到的使用情況。這就意味著出現(xiàn)新情況的時候,就可能提供新的update()方法,或者是干脆重新實現(xiàn)觀察者;而拉模型就不會造成這樣的情況,因為拉模型下,update()方法的參數(shù)是主題對象本身,這基本上是主題對象能傳遞的最大數(shù)據(jù)集合了,基本上可以適應(yīng)各種情況的需要。

六、優(yōu)點和缺點

6.1、優(yōu)點

  • 觀察者模式可以實現(xiàn)表示層和數(shù)據(jù)邏輯層的分離,并定義了穩(wěn)定的消息更新傳遞機(jī)制,抽象了更新接口,使得可以有各種各樣不同的表示層作為具體觀察者角色。
  • 觀察者模式在觀察目標(biāo)和觀察者之間建立一個抽象的耦合。
  • 觀察者模式支持廣播通信。
  • 觀察者模式符合“開閉原則”的要求。

6.2、缺點

  • 如果一個觀察目標(biāo)對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
  • 如果在觀察者和觀察目標(biāo)之間有循環(huán)依賴的話,觀察目標(biāo)會觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。
  • 觀察者模式?jīng)]有相應(yīng)的機(jī)制讓觀察者知道所觀察的目標(biāo)對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標(biāo)發(fā)生了變化。

七、適用環(huán)境

在以下情況下可以使用觀察者模式:

  • 一個抽象模型有兩個方面,其中一個方面依賴于另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和復(fù)用。
  • 一個對象的改變將導(dǎo)致其他一個或多個對象也發(fā)生改變,而不知道具體有多少對象將發(fā)生改變,可以降低對象之間的耦合度。
  • 一個對象必須通知其他對象,而并不知道這些對象是誰。
  • 需要在系統(tǒng)中創(chuàng)建一個觸發(fā)鏈,A對象的行為將影響B(tài)對象,B對象的行為將影響C對象……,可以使用觀察者模式創(chuàng)建一種鏈?zhǔn)接|發(fā)機(jī)制。

八、模式應(yīng)用

觀察者模式在軟件開發(fā)中應(yīng)用非常廣泛,如某電子商務(wù)網(wǎng)站可以在執(zhí)行發(fā)送操作后給用戶多個發(fā)送商品打折信息,某團(tuán)隊?wèi)?zhàn)斗游戲中某隊友犧牲將給所有成員提示等等,凡是涉及到一對一或者一對多的對象交互場景都可以使用觀察者模式。

九、總結(jié)

  • 觀察者模式定義對象間的一種一對多依賴關(guān)系,使得每當(dāng)一個對象狀態(tài)發(fā)生改變時,其相關(guān)依賴對象皆得到通知并被自動更新。觀察者模式又叫做發(fā)布-訂閱模式、模型-視圖模式、源-監(jiān)聽器模式或從屬者模式。觀察者模式是一種對象行為型模式。
  • 觀察者模式包含四個角色:目標(biāo)又稱為主題,它是指被觀察的對象;具體目標(biāo)是目標(biāo)類的子類,通常它包含有經(jīng)常發(fā)生改變的數(shù)據(jù),當(dāng)它的狀態(tài)發(fā)生改變時,向它的各個觀察者發(fā)出通知;觀察者將對觀察目標(biāo)的改變做出反應(yīng);在具體觀察者中維護(hù)一個指向具體目標(biāo)對象的引用,它存儲具體觀察者的有關(guān)狀態(tài),這些狀態(tài)需要和具體目標(biāo)的狀態(tài)保持一致。
  • 觀察者模式定義了一種一對多的依賴關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個目標(biāo)對象,當(dāng)這個目標(biāo)對象的狀態(tài)發(fā)生變化時,會通知所有觀察者對象,使它們能夠自動更新。
  • 觀察者模式的主要優(yōu)點在于可以實現(xiàn)表示層和數(shù)據(jù)邏輯層的分離,并在觀察目標(biāo)和觀察者之間建立一個抽象的耦合,支持廣播通信;其主要缺點在于如果一個觀察目標(biāo)對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間,而且如果在觀察者和觀察目標(biāo)之間有循環(huán)依賴的話,觀察目標(biāo)會觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。
  • 觀察者模式適用情況包括:一個抽象模型有兩個方面,其中一個方面依賴于另一個方面;一個對象的改變將導(dǎo)致其他一個或多個對象也發(fā)生改變,而不知道具體有多少對象將發(fā)生改變;一個對象必須通知其他對象,而并不知道這些對象是誰;需要在系統(tǒng)中創(chuàng)建一個觸發(fā)鏈。
  • 在JDK的java.util包中,提供了Observable類以及Observer接口,它們構(gòu)成了Java語言對觀察者模式的支持。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容