觀察者模式 (Observer)

1.定義

定義對象間的一種一對多的依賴關系。當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。

2.類圖

image
  • Subject

目標對象。具備功能點:1.管理多個觀察者。2.提供對觀察者的注冊及退訂。3.當狀態(tài)發(fā)生改變時,對訂閱有效的觀察者進行通知。

  • Observer

觀察者接口。定義目標通知后更新操作

  • ConcreteSubject

具體的主題,用來維護自身的狀態(tài)的變化

  • ConcreteObserver

觀察者具體實現(xiàn)對象,用來接受目標的通知,并進行后續(xù)的操作。

3.深入理解

  • 1.目標和觀察者的關系按定義區(qū)分:一對多。
  • 2.單向依賴關系。只有觀察者依賴與目標,目標不會依賴與觀察者。

建議:

  • 目標接口定義,建議在類名稱后面加上Subject
  • 觀察者接口定義,建議在類名稱后面加上Observer
  • 觀察者接口方法,建議命名為update

4.代碼

  • Subject
/**
 * 目標對象,它知道觀察它的觀察者,并提供注冊和刪除觀察者的接口
 */
public class Subject {

    /**
     * 用于保存觀察者對象
     */
    private List<Observer> observers = new ArrayList<>();

    /**
     * 注冊觀察者
     *
     * @param observer
     */
    public void attach(Observer observer) {
        observers.add(observer);
    }

    /**
     * 刪除觀察者對象
     *
     * @param observer
     */
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    /**
     * 通知所有注冊的觀察者對象
     */
    protected void notifyObservers() {
        observers.stream().forEach(observer -> observer.update(this));
    }
}
  • Observer
/**
 * 觀察者接口,定義一個更新的接口給那些在目標發(fā)生改變的時候被通知的后的操作
 */
public interface Observer {

    /**
     * 更新接口
     *
     * @param subject
     */
    void update(Subject subject);
}

  • ConcreteSubject
/**
 * 具體的目標對象,負責把有關狀態(tài)存入到相應的觀察者
 * 并在自己狀態(tài)發(fā)生改變時,通知各個觀察者
 */
public class ConcreteSubject extends Subject {

    /**
     * 示意目標對象
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        System.out.println("目標對象已更新自身狀態(tài)為:" + this.subjectState);
        this.notifyObservers();
    }
}
  • ConcreteObserver
/**
 * 具體的觀察者對象,實現(xiàn)更新方法,使得自身的狀態(tài)和目標狀態(tài)保持一致
 */
public class ConcreteObserver implements Observer {

    /**
     * 示意,觀察者對象狀態(tài)
     */
    private String observerState = "呀,我是觀察者初始狀態(tài)!";

    @Override
    public void update(Subject subject) {
        System.out.println("觀察者之前的狀態(tài)為:" + this.observerState);
        observerState = ((ConcreteSubject) subject).getSubjectState();
        System.out.println("觀察者接收到更新的對象狀態(tài):" + observerState);
    }
}

  • ObserverClient
/**
 * 調用示例
 */
public class ObserverClient {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer=new ConcreteObserver();
        subject.attach(observer);
        subject.setSubjectState("121");
    }
}

5. 觀察者兩種通知模式(推模型和拉模型)

5.1 推模型

目標對象主動向觀察者推送目標的詳細信息,不管觀察者是否需要,推送的信息通常是目標對象的全部或者部分數據,相當于在廣播。

5.2 拉模型

目標對象在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到目標對象中獲取,相當于是觀察者從目標對象中拉數據。

拉模型的處理方式,會把目標對象自身通過update方法傳遞給觀察者,這樣在觀察者需要獲取數據的時候,就可以通過這個引用來獲取了。

上面的代碼就是典型的拉模型。在具體的主題里,通過update方法把自身傳遞給具體的觀察者。

5.3 如何將上面的拉模型轉變?yōu)橥颇P?/h4>

處理步驟

  • 1.主題類的批量通知方法,增加需要通知的參數
  • 2.具體觀察者的update方法接受具體通知信息
  • PushSubject
public class PushSubject {
    private List<PushObserver> observers = new ArrayList<>();

    /**
     * 訂閱主題
     *
     * @param observer
     */
    public void attach(PushObserver observer) {
        observers.add(observer);
    }

    /**
     * 取消訂閱
     *
     * @param observer
     */
    public void detach(PushObserver observer) {
        observers.remove(observer);
    }

    /**
     * 拉模型通知所有的注冊者
     */
    protected void notifyObservers(String content) {
        observers.stream().forEach(observer -> {observer.update(content);});
    }
}

  • ConcretePushSubject
public class ConcretePushSubject extends PushSubject {
    /**
     * 示意目標對象
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        System.out.println("目標對象已更新自身狀態(tài)為:" + this.subjectState);
        this.notifyObservers(subjectState);
    }
}

  • PushObserver
public interface PushObserver {
    /**
     * 更新接口
     * 傳入更新的內容
     * @param content
     */
    void update(String content);
}

  • ConcretePushObserver
public class ConcretePushObserver implements PushObserver {
    @Override
    public void update(String content) {
        System.out.print("主題推送的信息:" + content);
    }
}
  • Main
public static void main(String[] args) {
    ConcretePushSubject pushSubject=new ConcretePushSubject();
    ConcretePushObserver pushObserver=new ConcretePushObserver();
    pushSubject.attach(pushObserver);
    pushSubject.setSubjectState("1212");
}

5.4 如何選擇拉模型與推模型

推模型是假定主題對象知道觀察者需要的數據。退模型的一個缺點就是:可能會使得觀察者對象難以復用,因為觀察者對象的update方法是按需而定義的。ps:為了避免這樣的問題,主題對象通常定義一個公用的話題。類比:微信公眾號,公眾號更新內容,直接推送給訂閱的用戶。

拉模型是主題對象無法得知觀察者具體需要什么數據,干脆把自身對象傳遞給觀察者,讓觀察者按需提取數據。

6.Java中的觀察者

Java語言已經內置好了觀察者的實現(xiàn)。

java.util包中定義了Observable,代表具備可以被觀察的能力。也就是我們之前定義的Subject類。目標接口

java.util包中也定義了一個接口Observer,代表觀察者對象接口。里面定義了update方法

6.1好處

  • 1.無須額外定義觀察者和目標對象的接口,JDK已經內置。
  • 2.具體的目標實現(xiàn)里面無須再維護觀察者的注冊信息,這個在Observable類中已經定義好
  • 3.觸發(fā)方式要先調用setChanged()方法
  • 4.具體的觀察者的實現(xiàn)里,update方法能夠同時支持推模型和拉模型。

6.2代碼

  • 1.定義主題類
import java.util.Observable;

public class NewPaper extends Observable {
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
        this.setChanged();
        //this.notifyObservers();//拉模型
        this.notifyObservers(content);//推模型
    }
}
  • 2.定義觀察者類
import java.util.Observable;
import java.util.Observer;

public class Reader implements Observer {

    //讀者名稱
    private String name;

    public Reader(String name) {
        this.name = 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 + "收到報紙了,主動獲取到目標更新的內容:" + ((NewPaper) o).getContent());
    }
}
  • 3.客戶端調用
public class Program {
    public static void main(String[] args) {
        NewPaper paper = new NewPaper();
        Reader zhangsan = new Reader("zhangsan");
        Reader lisi = new Reader("lisi");
        paper.addObserver(zhangsan);
        paper.addObserver(lisi);
        paper.setContent("iphone 8發(fā)布了?。?);
    }
}

7.觀察者優(yōu)缺點

7.1 優(yōu)點

  • 1.觀察者模式實現(xiàn)了觀察者與目標之間的抽象耦合
  • 2.觀察者模式實現(xiàn)了動態(tài)聯(lián)動
  • 3.觀察者模式實現(xiàn)了廣播通信。

7.2 缺點

  • 1.可能會引起無謂的操作

因為不管觀察者是否需要,都需要調用update方法。

8.觀察者的本質

觀察者的本質是:觸發(fā)聯(lián)動

當修改目標對象的狀態(tài)時候,就會觸發(fā)相應的通知,然后會循環(huán)調用所有注冊的觀察者對象的相應方法。相當于聯(lián)動調用這些觀察者的方法

這個聯(lián)動還是動態(tài)的,可以通過注冊和取消注冊來控制觀察者。同事目標對象和觀察者對象的解耦,又保證了無論觀察者發(fā)生怎樣的變化,目標對象總是能夠正確的聯(lián)動過來。

9.何時選用觀察者

  • 1.當一個抽象模型有兩個方面,一個方面依賴于另一個方面的狀態(tài)改變。

  • 2.在更改一個對象時候,需要同時連帶改變其他的對象,而且不知道究竟有多少對象需要被連帶改變。

  • 3.當一個對象必須通知其他的對象,但是又希望這個對象和其他被它通知的對象是松散耦合的。

10.與其他設計模式的區(qū)別

10.1 觀察者模式與狀態(tài)模式

兩者是有相似之處。但也有所不同,觀察者重心是觸發(fā)聯(lián)動。但是到底決定哪些觀察者進行聯(lián)動,可以配合狀態(tài)模式。

  • 觀察者模式

當目標狀態(tài)發(fā)生改變時,觸發(fā)并通知觀察者,讓觀察者去執(zhí)行相應的操作。

  • 狀態(tài)模式

根據不同的狀態(tài),選擇不同的實現(xiàn)。這個實現(xiàn)類的主要功能就是針對狀態(tài)相應的操作。

10.2 觀察者模式與中介者模式

兩個模式可以一起使用。比如觀察者模式里面的主題與觀察者交互比較復雜,那么就可以一起使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容