設計模式是被發現的,而非發明。
觀察者模式(發佈者-訂閱者模式)
定義
觀察者模式(又稱發佈者-訂閱者模式)定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。
設計原則
- 找出程序中變化的方面,然後將其跟固定不變的方面相分離
- 針對接口編程,而不是針對實現編程
- 更多的使用組合,少用繼承
- 爲了交互對象之間的鬆耦合設計努力,降低對象之間的互相依賴
使用場景舉例
我們要實現一個氣象站,當氣象站的數據(氣溫、氣壓等)有所變動時,就通知所有的顯示板更新顯示的狀態。
並且我們需要支持可以添加新的顯示板。
分析
爲了實現上述場景,我們需要將氣象站設定爲發佈者,將顯示板設定爲訂閱者(觀察者),
發佈者通過訂閱者實現的訂閱接口,可以隨時向所有訂閱者發佈消息。
示例代碼如下
// 氣象站(發佈者)
class WeatherData{
constructor() {
this.observerList=[]; //所有的觀察者
this.temp; //溫度
this.humidity;//溼度
this.pressure;//氣壓
}
// 註冊觀察者
registerObserver(observer){
this.observerList.push(observer);
}
// 刪除觀察者
removeObserver(observer){
var index = this.observerList.indexOf(observer);
if(index>=0){
this.observerList.splice(index,1);
}
}
// 當狀態改變時,調用此方法通知所有觀察者
notifyObserver(){
this.observerList.forEach(e=>{
e.update(this.temp,this.humidity,this.pressure);
})
}
// 修改氣象站數據時,通知觀察者
setWeatherData(temp,humidity,pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
this.notifyObserver();
}
}
// 顯示板(訂閱者)
class DispalyModul{
constructor(weatherData) {
this.temp; //溫度
this.humidity;//溼度
this.pressure;//氣壓
weatherData.registerObserver(this); //將訂閱者註冊到發佈者上,當發佈者進行發佈信息時,即可實現訂閱者收到信息
}
// 用來接收發布者消息
update(temp,humidity,pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
this.dispaly();
}
dispaly(){
console.log(`顯示板顯示:溫度-${this.temp},溼度-${this.humidity},氣壓-${this.pressure}`)
}
}
// Temp顯示板(訂閱者)
class DispalyTemp{
constructor(weatherData) {
this.temp; //溫度
this.humidity;//溼度
this.pressure;//氣壓
weatherData.registerObserver(this); //將訂閱者註冊到發佈者上,當發佈者進行發佈信息時,即可實現訂閱者收到信息
}
// 用來接收發布者消息
update(temp,humidity,pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
this.dispaly();
}
dispaly(){
console.log(`溫度顯示板:溫度-${this.temp}`)
}
}
// Humidity顯示板(訂閱者)
class DispalyHumidity{
constructor(weatherData) {
this.temp; //溫度
this.humidity;//溼度
this.pressure;//氣壓
weatherData.registerObserver(this); //將訂閱者註冊到發佈者上,當發佈者進行發佈信息時,即可實現訂閱者收到信息
}
// 用來接收發布者消息
update(temp,humidity,pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
this.dispaly();
}
dispaly(){
console.log(`溼度顯示板:溼度-${this.humidity}`)
}
}
// Pressure顯示板(訂閱者)
class DispalyPressure{
constructor(weatherData) {
this.temp; //溫度
this.humidity;//溼度
this.pressure;//氣壓
weatherData.registerObserver(this); //將訂閱者註冊到發佈者上,當發佈者進行發佈信息時,即可實現訂閱者收到信息
}
// 用來接收發布者消息
update(temp,humidity,pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
this.dispaly();
}
dispaly(){
console.log(`氣壓顯示板:氣壓-${this.pressure}`)
}
}
let subject = new WeatherData();
let observer = new DispalyModul(subject);
let observerTemp = new DispalyTemp(subject);
let observerHumidity = new DispalyHumidity(subject);
let observerPressure = new DispalyPressure(subject);
subject.setWeatherData(23.5,"45%",750);
// 輸出如下:
// 顯示板顯示:溫度-23.5,溼度-45%,氣壓-750
// 溫度顯示板:溫度-23.5
// 溼度顯示板:溼度-45%
// 氣壓顯示板:氣壓-750
subject.removeObserver(observerHumidity); //刪除溼度顯示板
subject.setWeatherData(31,"30%",950);
// 輸出如下:
// 顯示板顯示:溫度-31,溼度-30%,氣壓-950
// 溫度顯示板:溫度-31
// 氣壓顯示板:氣壓-950
我們使用JS來演示,其他語言思路相通,因JS中沒有接口概念,所以此處違背了設計原則中 針對接口編程,而不是針對實現編程的思想
爲了不違背針對接口編程的思想,此處應當將weatherData中的registerObserver、removeObserver、notifyObserver與dispalyModul中的update抽出寫成接口實現。這樣既可以保證訂閱者必須實現update方法,又能同時保證代碼具有鬆耦合的優點。
問題
可以看出,上述實現了觀察者模式的代碼中,有個明顯的問題
發佈者發佈的消息,訂閱者必須接受,不能做到訂閱者根據自己的需求獲取數據
這其實並不是問題,而是設計思路的不同。我們可以設計成當前這種向所有訂閱者發佈消息的模式,也可以設計成訂閱者向發佈者索取想要的數據,又或者也可以兩者兼容。
例如,我們需要訂閱者單獨獲取溫度信息,我們只需要在發佈者與訂閱者中分別添加如下代碼
// 氣象站(發佈者)
class WeatherData{
//```
getTemp(){
return this.temp
}
//```
}
// Temp顯示板(訂閱者)
class DispalyTemp{
constructor(weatherData) {
//```
this.weatherData = weatherData; // 保存發佈者
weatherData.registerObserver(this); //將訂閱者註冊到發佈者上,當發佈者進行發佈信息時,即可實現訂閱者收到信息
}
// ```
getTemp(){
return this.weatherData.getTemp();
}
// ```
}
let observerTemp = new DispalyTemp(subject);
observerTemp.getTemp();
// 輸出 weatherData中的temp,未定義的時候 就是undefined
總結一下
觀察者模式:發佈者提供註冊接口(registerObserver),所有人都可以通過此接口註冊成爲訂閱者,同時需要實現統一的訂閱函數(update),而後發佈者通過調用訂閱函數來給所有註冊過的觀察者發送消息。
最後
代碼是死的,人是活的,寫代碼的過程中,我們會發現很多種設計模式的變種,依照設計思路的不同,需求的不同,我們要靈活的運用設計模式,我們最終的目的,是爲了設計出彈性的、可複用的、可維護的項目。