設計模式 —– 觀察者模式
個人博客,想要搭建個人博客的可以進來看看: http://www.ioqian.top/
觀察者模式,在對象之間定義一對多的依賴,當一個對象狀態改變時,依賴他的對象就會收到通知。典型的實現是java swing中的組件監聽事件,當我們點擊按鈕時會調用我們註冊的回調函數;還有Rxjava等等
背景
我們是一個天氣臺,有許多客戶,當我們天氣信息更新時,我們需要通知用戶進行刷新…,我們要利用的是觀察者模式
觀察者模式主要由下面幾部分組成:
- Subject:抽象主題(抽象被觀察者),抽象主題角色把所有觀察者對象保存在一個集合裏,每個主題都可以有任意數量的觀察者,抽象主題提供一個接口,可以增加和刪除觀察者對象
- WeatherData :具體主題(具體被觀察者),該角色將有關狀態存入具體觀察者對象,在具體主題的內部狀態發生改變時,給所有註冊過的觀察者發送通知
- Observer:抽象觀察者,是觀察者者的抽象類,它是一個接口,裏面有一個很重要的方法,當具體主題狀態改變時,直接調用這個方法,這裏實現了松耦合
- BoardCallOne :具體觀察者,是實現抽象觀察者定義的更新接口,以便在得到主題更改通知時更新自身的狀態。
結合我們的背景和下面的代碼,我們天氣臺就是一個具體主題,我們的客戶就是具體觀察者,我們這裏抽象出了兩個接口是爲了實現松耦合
松耦合設計原則
當兩個對象松耦合時,他們可以互相交互,但是不太清楚彼此之間的細節,我們這裏讓主題和觀察者之間松耦合
我們怎麼利用接口實現的松耦合哪?
- 關於觀察者的一切,主題只知道觀察者實現了Observer接口,主題不需要知道具體的觀察者是誰,有什麼細節,只要繼承了Observer接口,有update()實現方法就可以
- 任何時候都可以增加新的觀察者,因爲主題僅僅依賴了Observer接口,我們可以隨時增加或者刪除觀察者
- 有新的觀察者時,主題的代碼不要修改
抽象主題 Subject
public interface Subject {
//該方法把任意的觀察者加入到觀察者集合
public void registerObserver(Observer o);
//該方法把觀察者從觀察者從集合中除去,從此收不到狀態改變
public void removeObserver(Observer o);
//當狀態改變時通過觀察者集合中的所以觀察者
public void notifyAllObserver();
}
抽象觀察者 Observer
public interface Observer {
public void update(float temp,float humidity);
}
具體主題 WeatherData
public class WeatherData implements Subject {
//觀察者集合
private List<Observer> observers;
//模擬自身狀態,當這些狀態改變時通知所有觀察者
private float temperature;
private float humidity;
public WeatherData(){
observers = new ArrayList<>();
}
//模擬狀態改變通知所有觀察者
public void stateChanged(float temp , float humidity){
this.temperature = temp;
this.humidity = humidity;
notifyAllObserver();
}
//模擬狀態改變通知所有觀察者
public void stateChanged(){
notifyAllObserver();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if(i>=0){
observers.remove(i);
}
}
@Override
public void notifyAllObserver() {
for(Observer o : observers){
//這裏很關鍵,這裏調用的是抽象觀察者的方法,不需要明白誰是觀察者,只要繼承了Observer的類就可以
o.update(temperature , humidity);
}
}
}
具體觀察者 BoardCallOne
public interface DisplayDevice {
public void display();
}
//繼承DisplayDevice接口和觀察者模式沒什麼聯繫
public class BoardCallOne implements Observer ,DisplayDevice {
private static final String TAG = "BoardCallOne";
private float temp;
private float humidity;
//抽象主題主題
private Subject weatherData ;
public BoardCallOne(Subject weatherData) {
this.weatherData = weatherData;
//作爲觀察者,註冊主題
weatherData.registerObserver(this);
}
//取消註冊
public void removeRegister(){
weatherData.removeObserver(this);
}
//作爲觀察者必須實現的方法,所有當具體主題狀態變化會通知我,我本身調用display()方法,實時刷新天氣信息
@Override
public void update(float temp, float humidity) {
this.temp = temp;
this.humidity = humidity;
display();
}
@Override
public void display() {
System.out.println(TAG + " i get info from weather data {temp="+temp+",humidity="+humidity+"}");
}
}
測試main
public class Main {
public static void main(String[] args) throws InterruptedException {
//具體主題實現這
WeatherData subject = new WeatherData();
//具體觀察者,在觀察者的構造方法中註冊了主題
BoardCallOne boardCallOne = new BoardCallOne(subject);
//在線程中一直讓主題去通知觀察者10次,每次休息1s
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0 ; i < 10 ;i++){
try {
subject.stateChanged(10.0f,18.0f);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
//主線程休眠5s後觀察者取消註冊,所以結果是會打印5次消息通知
Thread.sleep(5000);
boardCallOne.removeRegister();
//等待子線程結束
t.join();
}
}
結果驗證了我們的猜測
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
BoardCallOne i get info from weather data {temp=10.0,humidity=18.0}
Process finished with exit code 0
總結
1.我們這裏的是push模式,push模式是主題狀態改變去通知所有觀察者;還有pull,觀察者可以主動去獲取主題的狀態
2.觀察者模式用到了那些設計原則
- 變化分離原則,在觀察者模式中,會改變的是主題的狀態,及觀察者的類型和數目,我們可以改變改變依賴於主題的對象,不必改變主題
- 針對接口編程原則,主題和觀察者都使用了接口。觀察者利用主題的接口進行向主題註冊(在代碼具體觀察者的構造函數中);主題利用接口通知觀察者,實現了松偶爾
- 多用組合,少用繼承 ,利用組合把衆多觀察者包含進主題,private List observers;