設計模式是被發(fā)現(xiàn)的,而非發(fā)明。
觀察者模式(發(fā)布者-訂閱者模式)
定義
觀察者模式(又稱發(fā)布者-訂閱者模式)定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并自動更新。
設計原則
- 找出程序中變化的方面,然后將其跟固定不變的方面相分離
- 針對接口編程,而不是針對實現(xiàn)編程
- 更多的使用組合,少用繼承
- 為了交互對象之間的松耦合設計努力,降低對象之間的互相依賴
使用場景舉例
我們要實現(xiàn)一個氣象站,當氣象站的數(shù)據(jù)(氣溫、氣壓等)有所變動時,就通知所有的顯示板更新顯示的狀態(tài)。
并且我們需要支持可以添加新的顯示板。
分析
為了實現(xiàn)上述場景,我們需要將氣象站設定為發(fā)布者,將顯示板設定為訂閱者(觀察者),
發(fā)布者通過訂閱者實現(xiàn)的訂閱接口,可以隨時向所有訂閱者發(fā)布消息。
示例代碼如下
// 氣象站(發(fā)布者)
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);
}
}
// 當狀態(tài)改變時,調(diào)用此方法通知所有觀察者
notifyObserver(){
this.observerList.forEach(e=>{
e.update(this.temp,this.humidity,this.pressure);
})
}
// 修改氣象站數(shù)據(jù)時,通知觀察者
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); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
}
// 用來接收發(fā)布者消息
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); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
}
// 用來接收發(fā)布者消息
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); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
}
// 用來接收發(fā)布者消息
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); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
}
// 用來接收發(fā)布者消息
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中沒有接口概念,所以此處違背了設計原則中 針對接口編程,而不是針對實現(xiàn)編程的思想
為了不違背針對接口編程的思想,此處應當將weatherData中的registerObserver、removeObserver、notifyObserver與dispalyModul中的update抽出寫成接口實現(xiàn)。這樣既可以保證訂閱者必須實現(xiàn)update方法,又能同時保證代碼具有松耦合的優(yōu)點。
問題
可以看出,上述實現(xiàn)了觀察者模式的代碼中,有個明顯的問題
發(fā)布者發(fā)布的消息,訂閱者必須接受,不能做到訂閱者根據(jù)自己的需求獲取數(shù)據(jù)
這其實并不是問題,而是設計思路的不同。我們可以設計成當前這種向所有訂閱者發(fā)布消息的模式,也可以設計成訂閱者向發(fā)布者索取想要的數(shù)據(jù),又或者也可以兩者兼容。
例如,我們需要訂閱者單獨獲取溫度信息,我們只需要在發(fā)布者與訂閱者中分別添加如下代碼
// 氣象站(發(fā)布者)
class WeatherData{
//```
getTemp(){
return this.temp
}
//```
}
// Temp顯示板(訂閱者)
class DispalyTemp{
constructor(weatherData) {
//```
this.weatherData = weatherData; // 保存發(fā)布者
weatherData.registerObserver(this); //將訂閱者注冊到發(fā)布者上,當發(fā)布者進行發(fā)布信息時,即可實現(xiàn)訂閱者收到信息
}
// ```
getTemp(){
return this.weatherData.getTemp();
}
// ```
}
let observerTemp = new DispalyTemp(subject);
observerTemp.getTemp();
// 輸出 weatherData中的temp,未定義的時候 就是undefined
總結一下
觀察者模式:發(fā)布者提供注冊接口(registerObserver),所有人都可以通過此接口注冊成為訂閱者,同時需要實現(xiàn)統(tǒng)一的訂閱函數(shù)(update),而后發(fā)布者通過調(diào)用訂閱函數(shù)來給所有注冊過的觀察者發(fā)送消息。
最后
代碼是死的,人是活的,寫代碼的過程中,我們會發(fā)現(xiàn)很多種設計模式的變種,依照設計思路的不同,需求的不同,我們要靈活的運用設計模式,我們最終的目的,是為了設計出彈性的、可復用的、可維護的項目。