觀察者模式又叫做發布-訂閱模式,屬於行為型模式;觀察者模式通過定義一種一對多得依賴關系,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使他們能夠自動更新自己。
觀察者模式的UML類圖如下:
如上圖所示,觀察者模式主要涉及到抽象主題角色、具體主題角色、抽象觀察者角色、具體觀察者角色等四種角色:
- 抽象主題(Subject)角色:主題角色把所有對觀察者對象的引用保存在一個集合(比如 Vector對象)里,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,主題角色又叫做抽象被觀察者( Observable)角色,一般用一個抽象類或者一個接口實現。
- 具體主題(ConcreteSubject)角色:將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫作具體被觀察者角色(Concrete Observable)。具體主題角色通常用一個具體子類實現;具體主題角色負責實現對觀察者引用的聚集的管理方法。
- 抽象觀察者((Observer)角色:為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己。這個接口叫做更新接口。抽象觀察者角色一般用一個抽象類或者一個接口實現。
- 具體觀察者( ConcreteObserver)角色:存儲與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態相協調。如果需要,具體觀察者角色可以保存一個指向具體主題對象的引用。具體觀察者角色通常用一個具體子類實現。
天氣預報例子
現在看天氣預報已經成為我們生活中不可缺少的一個行為了。現在可以通過很多渠道查看到天氣情況,如新浪天氣,百度天氣,微信公眾號天氣等,這些查看天氣的都是從中國氣象局公布的天氣情況數據來的。在這以這個為例子講解觀察者模式。
例子的UML類圖如下:
抽象觀察者:
package com.charon.observer;
/**
* @className: Observer
* @description: 抽象觀察者
* @author: charon
* @create: 2022-03-30 23:15
*/
public interface Observer {
/**
* 更新天氣情況
* @param temperature 氣溫
* @param pressure 氣壓
* @param humidity 濕度
*/
void update(float temperature, float pressure, float humidity);
}
具體觀察者:
package com.charon.observer;
/**
* @className: CurrentConditions
* @description:
* @author: charon
* @create: 2022-03-30 23:17
*/
public class CurrentConditions implements Observer {
/**
* 氣溫,氣壓,濕度
*/
private float temperature, pressure, humidity;
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 模擬展示
display();
}
private void display() {
System.out.println("當前的氣溫是:" + temperature);
System.out.println("當前的氣壓是:" + pressure);
System.out.println("當前的濕度是:" + humidity);
}
}
package com.charon.observer;
/**
* @className: Baidu
* @description:
* @author: charon
* @create: 2022-03-30 23:20
*/
public class Baidu implements Observer{
/**
* 氣溫,氣壓,濕度
*/
private float temperature, pressure, humidity;
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 模擬展示
display();
}
private void display() {
System.out.println("百度天氣查詢得知,當前的氣溫是:" + temperature);
System.out.println("百度天氣查詢得知,當前的氣壓是:" + pressure);
System.out.println("百度天氣查詢得知,當前的濕度是:" + humidity);
}
}
抽象主題角色:
package com.charon.observer;
/**
* @className: Subject
* @description:
* @author: charon
* @create: 2022-03-30 23:24
*/
public interface Subject {
/**
* 注冊一個觀察者
* @param observer
*/
void registerObserver(Observer observer);
/**
* 刪除一個觀察者
* @param observer
*/
void removeObserver(Observer observer);
/**
* 通知觀察者
*/
void notifyObserver();
}
具體觀察者角色:
package com.charon.observer;
import java.util.ArrayList;
import java.util.List;
/**
* @className: WeatherData
* @description:
* @author: charon
* @create: 2022-03-30 23:27
*/
public class WeatherData implements Subject{
private List<Observer> observers;
/**
* 氣溫,氣壓,濕度
*/
private float temperature, pressure, humidity;
public WeatherData() {
this.observers = new ArrayList<>();
}
public void setWeatherData(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 模擬展示
notifyObserver();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
if(observers.contains(observer)){
observers.remove(observer);
}
}
@Override
public void notifyObserver() {
observers.forEach(item -> {
item.update(this.temperature,this.pressure,this.humidity);
});
}
}
client:
package com.charon.observer;
/**
* @className: Client
* @description: http://c.biancheng.net/view/1390.html
* @author: charon
* @create: 2022-03-30 22:56
*/
public class Client {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
// 注冊到具體觀察者上
weatherData.registerObserver(new CurrentConditions());
weatherData.registerObserver(new Baidu());
weatherData.setWeatherData(10f,100f,30f);
}
}
打印:
當前的氣溫是:10.0
當前的氣壓是:100.0
當前的濕度是:30.0
百度天氣查詢得知,當前的氣溫是:10.0
百度天氣查詢得知,當前的氣壓是:100.0
百度天氣查詢得知,當前的濕度是:30.0
如上圖的代碼所示,觀察者模式的例子就完成了,如果現在需要添加一個新浪天氣的廠商,那么之需要讓新浪天氣實現Observer接口,然后注冊到weatherData上就行了。
package com.charon.observer;
/**
* @className: sina
* @description:
* @author: charon
* @create: 2022-03-30 23:20
*/
public class sina implements Observer{
/**
* 氣溫,氣壓,濕度
*/
private float temperature, pressure, humidity;
@Override
public void update(float temperature, float pressure, float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 模擬展示
display();
}
private void display() {
System.out.println("新浪天氣查詢得知,當前的氣溫是:" + temperature);
System.out.println("新浪天氣查詢得知,當前的氣壓是:" + pressure);
System.out.println("新浪天氣查詢得知,當前的濕度是:" + humidity);
}
}
觀察者模式的主要優點如下:
- 降低了目標與觀察者之間的耦合關系,兩者之間是抽象耦合關系。被觀察者角色所知道的只是一個具體觀察者,每一個具體觀察者都符合一個抽象觀察者的接口,被觀察者並不認識任何一個具體觀察者,他只知道他們都有一個共同的接口,由於被觀察者和觀察者沒有緊密地耦合在一起,因此他們可以屬於不同地抽象化層次。
- 符合依賴倒置原則及開閉原則。
觀察者模式的主要缺點如下:
- 目標與觀察者之間的依賴關系並沒有完全解除,而且有可能出現循環引用。
- 當觀察者對象很多時,通知的發布會花費很多時間,影響程序的效率。
模式的應用場景
在軟件系統中,當系統一方行為依賴另一方行為的變動時,可使用觀察者模式松耦合聯動雙方,使得一方的變動可以通知到感興趣的另一方對象,從而讓另一方對象對此做出響應。
觀察者模式適合以下幾種情形:
- 對象間存在一對多關系,一個對象的狀態發生改變會影響其他對象。
- 當一個抽象模型有兩個方面,其中一個方面依賴於另一方面時,可將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用。
- 實現類似廣播機制的功能,不需要知道具體收聽者,只需分發廣播,系統中感興趣的對象會自動接收該廣播。
- 多層級嵌套使用,形成一種鏈式觸發機制,使得事件具備跨域(跨越兩種觀察者類型)通知。