觀察者模式(Observer)

觀察者模式又稱為發(fā)布訂閱模式。一個發(fā)布者對應多個訂閱者,一旦發(fā)布者的狀態(tài)發(fā)生改變時,訂閱者將收到訂閱事件。本文中涉及的代碼請點擊這里
先看看一個生活中的例子:

當我們想訂一份報紙,我們先去郵局找到報紙的編號后填寫訂閱單并繳費。當報社有新報紙發(fā)出時,郵局會將我們訂閱的報紙發(fā)給我們。

為了簡單我們去掉郵局環(huán)節(jié)簡化成:報社有新報紙后馬上通知用戶,這就是觀察者。
定義對象間的一對多關系,當一個對象的狀態(tài)發(fā)生變化時,所依賴于它的對象都得到通知并主動更新。在觀察者模式中,多個訂閱者成為觀察者(Observer),被觀察的對象成為目標(Subject)。觀察者的UML模型如下:

觀察者模式UML

先定義Subject并寫一個ConcreteSubject繼承Subject:

public class Subject {
    private List<Observer> observers = new ArrayList<Observer>();
    
    public void attach(Observer observer){
        observers.add(observer);
    }
    
    public void detach(Observer observer){
        observers.remove(observer);
    }
    
    public void notifyObservers(){
        for (Observer observer : observers) {
            observer.update(this);
        }
    }
}


public class ConcreteSubject extends Subject{
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        notifyObservers();
    }
}

再定義一個接口Observer,并寫一個ConcreteObserver實現(xiàn)Observer接口:

interface Observer {
    public void update(Subject subject);
}


public class ConcreteObserver implements Observer{
    private String name;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void update(Subject subject) {
        System.out.println(name + "狀態(tài):" + ((ConcreteSubject)subject).getSubjectState());
    }
}

最后看看主函數(shù)方法:

public class Client2 {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        ConcreteObserver observer1 = new ConcreteObserver();
        observer1.setName("張三");
        
        ConcreteObserver observer2 = new ConcreteObserver();
        observer2.setName("李四");
        
        ConcreteObserver observer3 = new ConcreteObserver();
        observer3.setName("王二");
        
        subject.attach(observer1);
        subject.attach(observer2);
        subject.attach(observer3);
        
        subject.setSubjectState("看完報紙");
    }
}

打印出來的結果:

張三狀態(tài):看完報紙
李四狀態(tài):看完報紙
王二狀態(tài):看完報紙

在實現(xiàn)觀察者模式的時候,一定要注意觸發(fā)通知的時機。一般情況下是在完成了狀態(tài)改變之后觸發(fā),因為通知會傳遞數(shù)據(jù),比如在setSubjectState時先通知觀測者就會發(fā)生錯誤。

// 錯誤寫法
public void setSubjectState(String subjectState) {
    notifyObservers();  // 通知應該放在狀態(tài)改變之后,因為 update(Subject subject) 中的參數(shù)類型為Subject 
    this.subjectState = subjectState;
}

在觀察者模式的實現(xiàn)上,有推模式和拉模式兩種方式:

  • 推模式
    Subject主動向Observer推送消息,不管對方是否需要,推送的信息通常是目標對象的全部或部分數(shù)據(jù),相當于廣播通信。
  • 拉模型
    Subject在通知Observer時只傳遞少量信息,如果觀察者需要更具體的信息,再由Observer主動去拉取數(shù)據(jù)。這樣的模型實現(xiàn)中會把Subject自身通過update方法傳入到Observer。

當前上面的實現(xiàn)使用的就是拉模型。通過(ConcreteSubject)subject得到具體對象,獲得信息。

當然Java本身就有觀察者模式的部分實現(xiàn),分別是java.util.Observable java.util.Observable
下面看一個使用Java自帶觀察者模式的例子:

新的目標直接繼承Java中定義的Observerable:

public class NewsPaperObservable extends Observable{
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
        setChanged();  // 必須調用這個方法來通知Observer狀態(tài)發(fā)生了改變
        notifyObservers(content);
    }
}

新的觀察者也直接實現(xiàn)Observer接口:

public class ReaderObserver implements Observer{
    private String name;
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + "讀取推送信息" + arg);
        System.out.println(name + "讀取拉取信息" + ((NewsPaperObservable)o).getContent());
    }
}

主函數(shù)和前面的相似:

public class Client {
    public static void main(String[] args) {
        NewsPaperObservable subject = new NewsPaperObservable();
        ReaderObserver reader1 = new ReaderObserver();
        reader1.setName("張三");
        
        ReaderObserver reader2 = new ReaderObserver();
        reader2.setName("李四");
        
        ReaderObserver reader3 = new ReaderObserver();
        reader3.setName("王二");
        
        subject.addObserver(reader1);
        subject.addObserver(reader2);
        subject.addObserver(reader3);
        
        subject.setContent("粗大事啦啦啦");
    }
}

打印出結果:

王二讀取推送信息粗大事啦啦啦
王二讀取拉取信息粗大事啦啦啦
李四讀取推送信息粗大事啦啦啦
李四讀取拉取信息粗大事啦啦啦
張三讀取推送信息粗大事啦啦啦
張三讀取拉取信息粗大事啦啦啦

使用Java自帶的觀察者模式需要注意以下幾個問題:

  • 具體的目標實現(xiàn)中不需要再維護觀察者的注冊信息了,這個在Java中的Observable類中實現(xiàn)了。
  • 觸發(fā)通知方式有些變化,需要先調用setChanged方法。
  • 具體的觀察者中,update()方法其實能同時支持推模型和拉模型。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容