1.觀察者模式-----Head First (設計模式進階)

認識

觀察者模式:出版者(主題)+訂閱者(觀察者)

使用場景:出版者發生動作,通知訂閱者發出相應的動作.這裏動作是抽象的,可以是狀態,也可以是其他的,出版者與訂閱者一般爲一對多的關係.

首先:我們來分析一下主題與觀察者的職能:

  • 主題:
    改變狀態,註冊觀察者與註銷觀察者(管理觀察者與自己的關係),通知觀察者狀態改變.
  • 觀察者:
    更新自己的狀態(動作)

那麼,問題來了,爲什麼把管理兩者關係的方法放在主題裏面呢?爲什麼不放在觀察者這裏?

首先,主題與觀察者是一對多的關係,主題是主動方,觀察者是被動方,如果將兩者的關係放在觀察者這裏,就沒辦法保證在主題狀態改變時通知註冊了的觀察者了,因爲這時主題是不知道哪些觀察者註冊進來的.

實踐

先上代碼:

主題:

主題:
/**
 * 主題 接口
 */
public interface Subject {

    /**
     * 註冊觀察者
     */
    void regist(Observer observer);

    /**
     * 註銷觀察者
     */
    void logout(Observer observer);

    /**
     * 通知 觀察者
     */

    void notifyAllObservers();

}
//抽象類
public abstract class AbstractSubject implements Subject  {

    /**
     *  狀態值
     */
    String status;
    /**
     *  觀察者列表
     */
    LinkedHashSet<Observer>observers;

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public LinkedHashSet<Observer> getObservers() {
        return observers;
    }

    public void setObservers(LinkedHashSet<Observer> observers) {
        this.observers = observers;
    }

    public AbstractSubject(String status, LinkedHashSet<Observer> observers) {
        this.status = status;
        this.observers = observers;
    }
}
//具體實現
public class ConSubject extends AbstractSubject {


    public ConSubject(String status, LinkedHashSet<Observer> observers) {
        super(status, observers);
    }

    @Override
    public void regist(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void logout(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyAllObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }

    @Override
    public void setStatus(String status) {
        super.setStatus(status);
        notifyAllObservers();
    }

}

觀察者:

//接口
public interface Observer {


    /**
     *  更新主題的變化
     */

    void update();
}
//具體實現類
public class ConObserver implements Observer {

    private ConSubject subject;

    private String status;

    @Override
    public void update() {
        status=subject.getStatus();
        System.out.println("subject: "+subject+";status:"+status);
    }

    public ConObserver(ConSubject subject) {
        this.subject = subject;
        subject.regist(this);
    }

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(ConSubject subject) {
        this.subject = subject;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }


}

測試demo:

public class Main {

    public static void main(String[] args){

        ConSubject subject=new ConSubject("wo",new LinkedHashSet<>());
        ConObserver conObserver=new ConObserver(subject);
        subject.setStatus("sss");
    }
}

幾個角色關係很簡單,類圖我就不畫了,直接分析一下:
首先我們從測試demo來分析,主題的構造方法初始化主題,我是用的linkedHashset來當盛放觀察者的容器,原因是可以自動去重,以及是有序的,當然你可以換成其他的容器,然後是觀察者的構造方法,傳入主題對象.在構造方法中,我們將其本身通過傳入的主題的方法註冊到了主題的容器中.這裏體現出組合的作用,然後,subject通過調用改變狀態的方法setStatus來改變狀態,並通知觀察者狀態已經改變,觀察者調用update方法改變本身的狀態.

Java 內置的觀察者模式接口

//主題 java 只提供了抽象類Observable,並沒有提供接口
//這裏都是java.util包下的接口和類
public class WeatherData extends Observable {

    private float temperature;

    private float humidity;

    private float pressure;

    public void setState(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        //標註已改變,設置改變按鈕的狀態 ,該狀態值用於開啓通知時作爲控制,否則就會不可控,
        // 例如在這裏我們可以設置當溫度超過12度,再通知觀察者變化,也就是控制靈敏度
        setChanged();
        //通知觀察者狀態已經改變
        notifyObservers();
    }

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }
}
// 展示類(功能類)
public interface DisplayElement {

    /**
     * 展示主題數據
     */
    void display();
}
//觀察者  
public class CurrentConditionsDisplay implements Observer,DisplayElement {

    private float temperature;

    private float humidity;

    Observable weatherData;


    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData){
            WeatherData weatherData=(WeatherData)o;
            this.humidity=weatherData.getHumidity();
            this.temperature=weatherData.getTemperature();
            display();
        }

    }


    @Override
    public void display() {
        System.out.println("temperature:"+this.temperature+";humidity:"+this.humidity);
    }


    public CurrentConditionsDisplay(Observable weatherData) {
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }
}
//測試demo
public class Main {

    public static void main(String[] args){

        WeatherData weatherData=new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay(weatherData);
        weatherData.setState(1.2f,2.2f,3.2f);
        weatherData.setState(1.5f,2.3f,3.5f);

    }
}

我們還是從測試demo來分析,其實,我們可以對比自己寫的與java內置的有什麼不同.

首先,主題只有抽象類observable,使用的容器是Vector,多了一個布爾型參數changed.

那麼,爲什麼只有抽象類,而沒有接口呢?那麼抽象類相比於接口來說無疑不夠靈活,不能多繼承也限制了其擴展,只不過,因爲主題是必須擁有觀察者的容器作爲屬性的,所以,即使定義了接口,也必須繼承這個抽象類,個人認爲出於這種情況的考慮,所以沒有定義接口.

另外,使用的是Vector容器,這點可以從源碼來看,爲了更安全,防止出現多線程問題.當然,你可以使用其他的,具體看業務場景.

最後,參數changed 有什麼作用?我們打開源碼,可以看到,有一個setChanged的方法,當主題改變時,我們爲了控制其通知的靈敏度(如果當溫度狀態變化太小,我們需要超過某個閾值的時候再通知觀察者,就需要這麼一個作爲開關作用的屬性)而設置,否則,只要主題狀態改變,就會通知到觀察者.

總結

封裝變化,多組合,少繼承,面向接口編程.

觀察者模式與發佈訂閱模式的區別:

1.在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在發佈訂閱模式中,發佈者和訂閱者不知道對方的存在。它們只有通過消息代理進行通信。

2.在發佈訂閱模式中,組件是鬆散耦合的,正好和觀察者模式相反。

3.觀察者模式大多數時候是同步的,比如當事件觸發,Subject就會去調用觀察者的方法。而發佈-訂閱模式大多數時候是異步的(使用消息隊列)。

4.觀察者模式需要在單個應用程序地址空間中實現,而發佈-訂閱更像交叉應用模式。

具體的解釋可以查看博文:https://blog.csdn.net/Firvana_Mutex/article/details/82696406

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