認識
觀察者模式:出版者(主題)+訂閱者(觀察者)
使用場景:出版者發生動作,通知訂閱者發出相應的動作.這裏動作是抽象的,可以是狀態,也可以是其他的,出版者與訂閱者一般爲一對多的關係.
首先:我們來分析一下主題與觀察者的職能:
- 主題:
改變狀態,註冊觀察者與註銷觀察者(管理觀察者與自己的關係),通知觀察者狀態改變. - 觀察者:
更新自己的狀態(動作)
那麼,問題來了,爲什麼把管理兩者關係的方法放在主題裏面呢?爲什麼不放在觀察者這裏?
首先,主題與觀察者是一對多的關係,主題是主動方,觀察者是被動方,如果將兩者的關係放在觀察者這裏,就沒辦法保證在主題狀態改變時通知註冊了的觀察者了,因爲這時主題是不知道哪些觀察者註冊進來的.
實踐
先上代碼:
主題:
主題:
/**
* 主題 接口
*/
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