在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等。在软件世界也是这样,例如MVC 模式中的模型与视图的关系,事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。
当对象间存在一种一对多的依赖关系,则 可以使用观察者模式(Observer Pattern),当这个对象被修改时,则会自动通知依赖它的所有对象。观察者模式属于行为型模式。
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
观察者模式包含主要以下角色:
抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
场景:假如,你是个气象局的程序员,现在要设计一个程序,可以实时获取当前的天气值(温度,湿度,气压等),当当前温度和上次温度数据相比大于5度,就会及时更新3个画板上的数据(每个画板不一样)
按需求画出类图
//SubJect.ts
import { IObserver } from "./IObserver"
export abstract class SubJect {
protected observerArray: Array<IObserver> = [];
//注册观察者
public registerObserver(Observer: pattern.IObserver): void {
this.observerArray.push(Observer);
}
//移除观察者
public removerObserver(Observer: pattern.IObserver): void {
let index = this.observerArray.indexOf(Observer);
this.observerArray.splice(index, 1);
}
public abstract notifyObserver(): void;
}
2.创建一个具体的类WeatherData去继承抽象类SubJect
//WeatherData.ts
import { SubJect } from "./SubJect"
//可观察者observerable,具体主题
export class WeatherData extends SubJect {
//温度
private temperature: number;
//气压
private pressure: number;
//湿度
private humidity: number;
//状态
private staus: boolean;
constructor() {
super();
//默认状态是false
this.staus = false;
}
//通知观察者
public notifyObserver(): void {
if (this.staus) {
for (let i = 0; i < this.observerArray.length; i++) {
this.observerArray[i].update(this);
}
}
}
//获取数据
public getData(temperature: number, pressure: number, humidity: number): void {
//如果没有数据 或者两次的数据相差大于5就去改变状态,并通知观察者,通知完之后把状态改成false
if ((this.temperature == undefined && this.pressure == undefined && this.humidity == undefined) || this.temperature && this.pressure && this.humidity && Math.abs(this.temperature - temperature) >= 5) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
this.stausChange(true);
//通知完成之后把状态改成false
this.notifyObserver();
this.stausChange(false);
}
}
//改装状态
public stausChange(staus: boolean): boolean {
return this.staus = staus;
}
//获取温度,由观察者去拉去,而不是推送
public getTemperature(): number {
return this.temperature;
}
//获取气压,由观察者去拉去,而不是推送
public getPressure(): number {
return this.pressure;
}
//获取湿度,由观察者去拉去,而不是推送
public getHumidity() {
return this.humidity;
}
}
3.创建观察者接口 interface IObserver
//IObserver.ts
export interface IObserver {
update(weatherData:pattern.WeatherData): void;
display(): void;
}
4.分别创建3个画板类去实现interface IObserver,这里为简单测试,我把其他两个画板的显示数据一个加5度,一个减5度
//OneDisplay.ts
import { IObserver } from "./IObserver"
//观察者observer OneDisplay
export class OneDisplay implements IObserver {
private temperature: number;
private pressure: number;
private humidity: number;
//观察者更新数据
public update(WeatherData: pattern.WeatherData): void {
this.temperature = WeatherData.getTemperature();
this.pressure = WeatherData.getPressure();
this.humidity = WeatherData.getHumidity();
this.display();
}
//画板显示
display(): void {
console.log('我是第一个画板显示,温度' + this.temperature + "气压" + this.pressure + "湿度" + this.humidity)
};
}
//TwoDisplay.ts
import { IObserver } from "./IObserver"
//观察者observer TwoDisplay
export class TwoDisplay implements IObserver {
private temperature: number;
private pressure: number;
private humidity: number;
//观察者更新数据
public update(WeatherData: pattern.WeatherData): void {
this.temperature = WeatherData.getTemperature() + 5;
this.pressure = WeatherData.getPressure() + 5;
this.humidity = WeatherData.getHumidity() + 5;
this.display();
}
//画板显示
display(): void {
console.log('我是第二个画板显示,温度' + this.temperature + "气压" + this.pressure + "湿度" + this.humidity)
};
}
//ThreeDisplay.ts
import { IObserver } from "./IObserver"
//观察者observer ThreeDisplay
export class ThreeDisplay implements IObserver {
private temperature: number;
private pressure: number;
private humidity: number;
//观察者更新数据
public update(WeatherData: pattern.WeatherData): void {
this.temperature = WeatherData.getTemperature() - 5;
this.pressure = WeatherData.getPressure() - 5;
this.humidity = WeatherData.getHumidity() - 5;
this.display();
}
//画板显示
display(): void {
console.log('我是第三个画板显示,温度' + this.temperature + "气压" + this.pressure + "湿度" + this.humidity)
};
}
5.最后在indexContriller里面去调用
import angular from "angular";
import { WeatherData } from "./WeatherData"
import { OneDisplay } from "./OneDisplay"
import { TwoDisplay } from "./TwoDisplay"
import { ThreeDisplay } from "./ThreeDisplay"
export class IndexController implements angular.IController {
public instance: pattern.WeatherData;
constructor() {
//创建可观察者(主题)实例
this.instance = new WeatherData();
//创建观察者数组
let boardArray: Array<pattern.IObserver> = [new OneDisplay(), new TwoDisplay(), new ThreeDisplay()]
//注册观察者
if (boardArray.length != 0) {
for (let i = 0; i < boardArray.length; i++) {
this.instance.registerObserver(boardArray[i]);
}
};
//可观察者(主题)实例设置数据(第一次显示)
this.instance.getData(20, 20, 20);
console.log("-----------------------------------------------------------------")
//当前温度数据和上次数据相比大于5才会通知观察者,所以不显示
this.instance.getData(21, 21, 21);
console.log("-----------------------------------------------------------------")
//大于5,所以显示
this.instance.getData(30, 21, 21);
}
}
这里测试了3次数据,第一次状态改变会通知观察者更新数据并显示,第二次和第一次的温度才相差一,状态不改变,不会通知观察者,第三次和第二次数据相差9,会改变会通知观察者更新数据并显示,所以最终显示两组数据!看下图:
总结一下优缺点:
优点:
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
目标与观察者之间建立了一套触发机制。
缺点:
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。