设计模式是被发现的,而非发明。
观察者模式(发布者-订阅者模式)
定义
观察者模式(又称发布者-订阅者模式)定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
设计原则
- 找出程序中变化的方面,然后将其跟固定不变的方面相分离
- 针对接口编程,而不是针对实现编程
- 更多的使用组合,少用继承
- 为了交互对象之间的松耦合设计努力,降低对象之间的互相依赖
使用场景举例
我们要实现一个气象站,当气象站的数据(气温、气压等)有所变动时,就通知所有的显示板更新显示的状态。
并且我们需要支持可以添加新的显示板。
分析
为了实现上述场景,我们需要将气象站设定为发布者,将显示板设定为订阅者(观察者),
发布者通过订阅者实现的订阅接口,可以随时向所有订阅者发布消息。
示例代码如下
// 气象站(发布者)
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),而后发布者通过调用订阅函数来给所有注册过的观察者发送消息。
最后
代码是死的,人是活的,写代码的过程中,我们会发现很多种设计模式的变种,依照设计思路的不同,需求的不同,我们要灵活的运用设计模式,我们最终的目的,是为了设计出弹性的、可复用的、可维护的项目。