什麼是觀察者模式
觀察者模式(Observer Pattern)定義了對象之間的一對多依賴,讓多個觀察者對象同時監聽一個主體對象,當主體對象發生變化時,它的所有依賴者(觀察者)都會收到通 知並更新,屬於行爲型模式。觀察者模式有時也叫做發佈訂閱模式,主要用 於在關聯行爲之間建立一套觸發機制的場景
觀察者模式示例
假如我們需要從氣象臺獲取天氣信息用作展示,當天氣信息發生改變之後,我們也需要同步發生改變。
1、建立一個Subject接口
package com.zwx.design.pattern.observe.headfirst;
public interface Subject {
void registerObserver(Observer o);//註冊觀察對象
void removeObserver(Observer o);//移除觀察對象
void notifyObservers();//通知觀察對象
}
2、建立一個WeatherData(被觀察者)實現Subject接口(面向接口編程會更易於擴展,這裏也可以不用Subject接口,直接建立WeatherData實現)
package com.zwx.design.pattern.observe.headfirst;
import java.util.ArrayList;
import java.util.List;
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;//溫度
private float humidity;//溼度
private float pressure;//氣壓
public void setMessurements(float temperature,float humidity,float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();//氣溫信息發生變化時,通知所有觀察者
}
public WeatherData() {
this.observers = new ArrayList<>();
}
/**
* 註冊觀察者
* @param o
*/
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
/**
* 移除觀察者
* @param o
*/
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if(i >= 0){
observers.remove(i);
}
}
/**
* 通知所有觀察者
*/
@Override
public void notifyObservers() {
for (Observer observer : observers){
observer.update(temperature,humidity,pressure);
}
}
}
3、建立一個***觀察者***接口
package com.zwx.design.pattern.observe.headfirst;
public interface Observer {
void update(float temperature,float humidity,float pressure);
}
這個接口只定義了一個update方法,用於更新氣象數據
4、建立一個WeatherDisplay(觀察者)類實現Observer接口
package com.zwx.design.pattern.observe.headfirst;
public class WeatherDisplay implements Observer {
private Subject subject;
private float temperature;//溫度
private float humidity;//溼度
private float pressure;//氣壓
public WeatherDisplay(Subject subject) {//註冊監聽對象
this.subject = subject;
subject.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
}
public void display(){
System.out.println("當前最新的溫度爲:" + temperature + ",溼度爲:" + humidity +
",氣壓爲:" + pressure);
}
}
5、最後再寫一個測試類測試:
package com.zwx.design.pattern.observe.headfirst;
public class WeatherDataTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();//氣象數據即被觀察者
WeatherDisplay weatherDisplay = new WeatherDisplay(weatherData);//天氣展示即觀察者
weatherData.setMessurements(37.2f,80f,32.5f);
weatherDisplay.display();//天氣展示
}
}
最後輸出結果爲:
說明被觀察者WeatherData氣象發生變化時,觀察者WeatherDisplay也及時獲得了最新的數據變化,從而實現了一個簡單的觀察者模式!
上面的示例中存在一種問題,那就是數據是被push(推)過來的,也就是說不管觀察者想不想要,只要氣象發生變化了,被觀察者就會把數據push(推)給觀察者,那麼能不能實現讓觀察者主動去pull(拉)呢?答案是肯定的,上面我們只要把WeatherData中每個數據都提供getter方法,然後再稍作改進就可以實現!
JDK中自帶實現了觀察者模式,並且實現了push(推)和pull(拉)兩種類型,接下來就讓我們用JDK自帶的觀察者模式來實現pull(拉)的場景。
JDK觀察者模式使用
假設我們在空間中發了一條動態,需要把這條動態推送給好友
1、首先建立一個Zone類(被觀察者)實現Observable類
package com.zwx.design.pattern.observe.trendsNotice;
import java.util.Observable;
public class Zone extends Observable {
public void publishTrends(Trends trends){
System.out.println(trends.getNickName() + "發表了一個動態【" + trends.getContent() + "】" );
setChanged();//僅作標識用
notifyObservers(trends);//通知所有觀察者
}
}
注意這裏的Observable是java.util包下面的,這個類裏面定義了一個發表動態的方法。這裏setChanged()僅僅只是把一個改變標識設置爲true
之所以要這一個,是因爲JDK中的notifyObservers方法中會判斷這個標識是否爲true,爲true纔會進行通知
2、定義一個動態類
package com.zwx.design.pattern.observe.trendsNotice;
public class Trends {
private String nickName;//發表動態的用戶暱稱
private String content;//發表的動態內容
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
3、定義一個Friends類(觀察者),實現Observer接口
package com.zwx.design.pattern.observe.trendsNotice;
import java.util.Observable;
import java.util.Observer;
public class Friends implements Observer {
private String friendName;
public String getFriendName() {
return friendName;
}
public void setFriendName(String friendName) {
this.friendName = friendName;
}
@Override
public void update(Observable o, Object arg) {
Trends trends = new Trends();
if(null != arg && arg instanceof Trends){
trends = (Trends)arg;
}
System.out.println(this.getFriendName() + ",您好!您收到了來自" + trends.getNickName() +
"的一條動態【" + trends.getContent() + "】" + "快去點贊吧!");
}
}
注意這裏的update方法中的第2個參數,如果上面Zone類中的方法publishTrends方法中調用的notifyObservers方法不傳入參數trends,這裏拿到的就是空,也就是相當於實現了push(推),傳入了參數我們update中就能拿到數據對象,這時候我們就可以主動去pull(拉)數據,只選擇自己想要的數據進行展示或者處理
4、最後寫一個測試類測試
package com.zwx.design.pattern.observe.trendsNotice;
public class ObserverTest {
public static void main(String[] args) {
Zone zone = new Zone();
Friends friends = new Friends();
friends.setFriendName("張三丰");
Trends trends = new Trends();
trends.setNickName("張無忌");
trends.setContent("祝太師傅長命百歲!");
zone.addObserver(friends);
zone.publishTrends(trends);
}
}
輸出結果爲如下:
說明發表動態之後朋友收到了通知,且拿到了動態相關數據,實現了觀察者模式pull(拉)數據
java.util.Observable的黑暗面
1、Observable是一個類而不是一個接口,所以就限制了它的使用和服用,如果某類同時想具有Observable類和另一個超類的行爲,就會有問題,畢竟java不支持多繼承。
2、Observable將關鍵的方法保護起來了,比如setChanged()方法,這意味着除非我們繼承自Observable,否則無法創建Observable實例並組合到我們自己的對象中來,這個設計違反了設計原則:多用組合,少用繼承。
觀察者模式的有優點
1、觀察者和被觀察者之間建立了一個抽象的耦合。
2、觀察者模式支持廣播通信
觀察者模式的有缺點
1、觀察者之間有過多的細節依賴、提高時間消耗及程序的複雜度。 2、使用要得當,要避免循環調用。