設計模式 —— 觀察者模式

鐵鏽的秀發表於2019-04-12

簡介

觀察者模式(Observer Pattern)屬於行為型模式的一種,它定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態變化時,會通知所有的觀察者物件,使他們能夠自動更新。觀察者模式的別名包括髮布-訂閱(Publish/Subscribe)模式、模型-檢視(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。

觀察者模式的推拉方式

  • 推方式:

主題物件向觀察者推送主題的詳細資訊,不管觀察者是否需要,推送的資訊通常是主題物件的全部或部分資料。

  • 拉方式:

主題物件在通知觀察者的時候,只傳遞少量資訊。如果觀察者需要更具體的資訊,由觀察者主動到主題物件中獲取,相當於是觀察者從主題物件中拉資料。一般這種模型的實現中,會把主題物件自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取資料的時候,就可以通過這個引用來獲取了。

例項

觀察者模式所涉及的角色有:

  1. 抽象主題角色(Subject):抽象主題角色把所有對觀察者物件的引用儲存在一個聚集(比如ArrayList物件)裡,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件,抽象主題角色又叫做抽象被觀察者(Observable)角色;

  2. 具體主題角色(ConcreteSubject):將有關狀態存入具體觀察者物件;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色;

  3. 抽象觀察者角色(Observer):為所有的具體觀察者定義一個介面,在得到主題的通知時更新自己,這個介面叫做更新介面;

  4. 具體觀察者角色(ConcreteObserver):儲存與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態像協調。如果需要,具體觀察者角色可以保持一個指向具體主題物件的引用。

  • 推方式:
// 抽象觀察者角色類
public interface Observer {
    void update(String message);
}
// 抽象主題角色類
public abstract class AbstractSubject {
    private List<Observer> list = new ArrayList<>();

    public void attach(Observer observer) {
        list.add(observer);
    }

    public void detach(Observer observer) {
        list.remove(observer);
    }

    public void notifyObserver(String message) {
        for (Observer observer : list) {
            observer.update(message);
        }
    }
}

// 具體觀察者角色類
public class WxUserObserver implements Observer {

    private String name;

    public WxUserObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + "收到推送訊息:" + message);
    }

}

// 具體主題角色類
public class WxSubject extends AbstractSubject {

    public void change(String message) {
        this.notifyObserver(message);
    }
}

// 客戶端
public class Client {
    @Test
    public void test() {
        WxSubject subject = new WxSubject();

        final WxUserObserver zs = new WxUserObserver("張三");
        final WxUserObserver ls = new WxUserObserver("李四");
        final WxUserObserver ww = new WxUserObserver("王五");

        // 初始化三個關注使用者
        subject.attach(zs);
        subject.attach(ls);
        subject.attach(ww);

        subject.change("開始推送訊息1");
        // 張三取消關注
        subject.detach(zs);
        subject.change("開始推送訊息2");
    }
}
複製程式碼
張三收到推送訊息:開始推送訊息1
李四收到推送訊息:開始推送訊息1
王五收到推送訊息:開始推送訊息1
李四收到推送訊息:開始推送訊息2
王五收到推送訊息:開始推送訊息2
複製程式碼
  • 拉方式:
// 抽象觀察者角色類
public interface Observer {
    void update(AbstractSubject subject);
}

// 抽象主題角色類
public abstract class AbstractSubject {
    private List<Observer> list = new ArrayList<>();

    public void attach(Observer observer) {
        list.add(observer);
    }

    public void detach(Observer observer) {
        list.remove(observer);
    }

    public void notifyObserver() {
        for (Observer observer : list) {
            observer.update(this);
        }
    }
}

// 具體觀察者角色類
public class WxUserObserver implements Observer {

    private String name;

    public WxUserObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + "收到推送訊息:" + message);
    }

}

// 具體主題角色類
public class WxSubject extends AbstractSubject {

    private String state;


    public String getState() {
        return state;
    }


    public void change(String state) {
        this.state = state;
        this.notifyObserver();
    }
}

// 客戶端
public class Client {
    @Test
    public void test() {
        WxSubject subject = new WxSubject();

        final WxUserObserver zs = new WxUserObserver("張三");
        final WxUserObserver ls = new WxUserObserver("李四");
        final WxUserObserver ww = new WxUserObserver("王五");

        // 初始化三個關注使用者
        subject.attach(zs);
        subject.attach(ls);
        subject.attach(ww);
        subject.change("State");

        // 李四取消關注
        subject.detach(ls);
        subject.change("new State");
    }
}
複製程式碼
張三狀態更新為:State
李四狀態更新為:State
王五狀態更新為:State
張三狀態更新為:new State
王五狀態更新為:new State
複製程式碼
  • JDK 實現:

Java語言的java.util庫裡面,提供了一個Observable類以及一個Observer介面,構成了Java語言對觀察者模式的支援。 Observer介面只定義了一個update()方法,當被觀察者物件的狀態發生變化時,被觀察者物件的notifyObservers()方法就會呼叫這一方法。 Observable類是被觀察者類的基類。java.util.Observable提供公開的方法支援觀察者物件,這些方法中有兩個對Observable的子類非常重要:一個是setChanged(),另一個是notifyObservers()。第一方法setChanged()被呼叫之後會設定一個內部標記變數,代表被觀察者物件的狀態發生了變化。第二個是notifyObservers(),這個方法被呼叫時,會呼叫所有登記過的觀察者物件的update()方法,使這些觀察者物件可以更新自己。

// 觀察者
public class WxUserObserver implements Observer {

    private String name;

    public WxUserObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + "狀態更新為:" + ((WxObservable) o).getState());
    }
}

// 被觀察者
public class WxObservable extends Observable {

    private String state;


    public String getState() {
        return state;
    }

    public void change(String state) {
        this.state = state;
        this.setChanged(); // 設定一個內部標記變數
        this.notifyObservers(); // 呼叫所有登記過的觀察者物件的update()方法
    }
}

public class Client {
    @Test
    public void test() {
        WxObservable observable = new WxObservable();

        final WxUserObserver zs = new WxUserObserver("張三");
        final WxUserObserver ls = new WxUserObserver("李四");
        final WxUserObserver ww = new WxUserObserver("王五");

        observable.addObserver(zs);
        observable.addObserver(ls);
        observable.addObserver(ww);
        observable.change("State");

        observable.deleteObserver(ls);
        observable.change("new State");
    }
}
複製程式碼
張三狀態更新為:State
李四狀態更新為:State
王五狀態更新為:State
張三狀態更新為:new State
王五狀態更新為:new State
複製程式碼

用JDK提供的這種方式能很好方便的實現,推方式和拉方式的設計模式。

類圖

推方式:

推方式

拉方式:

拉方式

JDK實現:

JDK實現

優點

  1. 觀察者和被觀察者是抽象耦合的;
  2. 建立一套觸發機制。

缺點

  1. 如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間;

  2. 如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰;

  3. 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

適用場景

  1. 一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的物件中使它們可以各自獨立地改變和複用;

  2. 一個物件的改變將導致其他一個或多個物件也發生改變,而不知道具體有多少物件將發生改變,可以降低物件之間的耦合度;

  3. 一個物件必須通知其他物件,而並不知道這些物件是誰;

  4. 需要在系統中建立一個觸發鏈,A物件的行為將影響B物件,B物件的行為將影響C物件……,可以使用觀察者模式建立一種鏈式觸發機制。

總結

觀察者模式用於建立一種物件與物件之間的依賴關係,一個物件發生改變時將自動通知其他物件,其他物件將相應作出反應。在 Java 中已經對觀察者模式的支援類 java.util.Observerjava.util.Observable,不需要我們自己實現。使用中注意避免迴圈引用。如果順序執行,某一觀察者錯誤會導致系統卡殼,一般採用非同步方式。

相關文章