观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新
观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯
例如生活中,我们可以用报纸期刊的订阅来形象的说明,当你订阅了一份报纸,每天都会有一份最新的报纸送到你手上,有多少人订阅报纸,报社就会发多少份报纸
报社和订报纸的客户就形成了一对多的依赖关系
被观察者知道观察者的存在,同时管理所有的观察者
class Observer {
update(params) {
console.log(params)
}
}
class Demo {
update(params) {
console.log(params)
}
}
class ObserverList {
constructor() {
this.observerList = []
}
add(observer) {
this.observerList.push(observer);
return
}
delete(observer) {
this.observerList = this.observerList.filter(ob => ob !== observer);
return this;
}
get(index) {
return this.observerList[index];
}
count() {
return this.observerList.length;
}
}
class Subject {
observers = new ObserverList;
add(observer) {
this.observers.add(observer)
}
remove(observer) {
this.observers.delete(observer);
}
notify(...params) {
for (let i = 0; i < this.observers.count(); i++) {
let item = this.observers.get(i)
item.update(...params)
}
}
}
let sub = new Subject()
sub.add(new Observer)
sub.add(new Observer)
sub.add(new Demo)
sub.notify('测试观察者模式发出通知')
在这个星型结构中,同事对象不再直接与其他的同事对象联系,通过中介者对象与另一个对象发生相互作用,中介者对象的存在保证了结构上的稳定,也就是说,系统的结构不会因为新对象的引入带来大量的修改工作。
如果一个系统中对象之间存在多对多的相互关系,可以将对象之间的一些交互行为从各个对象之间分离出来,并集中封装在一个中介者对象中,由中介者进行统一的协调,这样对象之间多对多的复杂关系就转变为相对简单的一对多关系,通过引入中介者来简化对象之间的复杂交互。
以租房为例,租房者和房主都通过中介更新信息,中介将更新后的信息通知对应的对象
class Tenant {
constructor(name, mediator) {
this.name = name;
this.mediator = mediator
}
contract(message) {
this.mediator.contract(message, this)
}
getMessage(message) {
console.log(message)
}
}
class HouseOwner {
constructor(name, mediator) {
this.name = name;
this.mediator = mediator
}
contract(message) {
this.mediator.contract(message, this)
}
getMessage(message) {
console.log(message)
}
}
class Mediator {
constructor(houseOwner, tenant) {
this.houseOwner = houseOwner
this.tenant = tenant
}
contract(message, person) {
if (person == this.houseOwner) {
this.tenant.getMessage(message)
} else {
this.houseOwner.getMessage(message)
}
}
getTenant() {
return this.tenant
}
setTenant(tenant) {
this.tenant = tenant
}
getHouseOwner() {
return this.houseOwner
}
setHouseOwner(houseOwner) {
this.houseOwner = houseOwner
}
}
let mediator = new Mediator()
let tenant = new Tenant('tenant',mediator)
let houseOwner = new HouseOwner('houseOwner',mediator)
mediator.setTenant(tenant)
mediator.setHouseOwner(houseOwner)
tenant.contract('你好房东 我是租客')
houseOwner.contract('你好租客 我是房东')
中介者类复杂:由于具体中介者中包含了大量的同事之间的交互细节,可能会导致具体中介者类变得非常复杂,使得系统难以维护
发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在
同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者存在
class PubSub {
constructor() {
this.messages = {};
this.listeners = {};
}
// 添加发布者
publish(type, content) {
const existContent = this.messages[type];
if (!existContent) {
this.messages[type] = [];
}
this.messages[type].push(content);
}
// 添加订阅者
subscribe(type, cb) {
const existListener = this.listeners[type];
if (!existListener) {
this.listeners[type] = [];
}
this.listeners[type].push(cb);
}
// 通知
notify(type) {
const messages = this.messages[type];
const subscribers = this.listeners[type] || [];
subscribers.forEach((cb, index) => cb(messages[index]));
}
}
class Publisher {
constructor(name, context) {
this.name = name;
this.context = context;
}
publish(type, content) {
this.context.publish(type, content);
}
}
class Subscriber {
constructor(name, context) {
this.name = name;
this.context = context;
}
subscribe(type, cb) {
this.context.subscribe(type, cb);
}
}
const TYPE_A = 'music';
const TYPE_B = 'movie';
const TYPE_C = 'novel';
const pubsub = new PubSub();
const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');
publisherA.publish(TYPE_B, 'the silicon valley');
const publisherB = new Publisher('publisherB', pubsub);
publisherB.publish(TYPE_A, 'stronger');
const publisherC = new Publisher('publisherC', pubsub);
publisherC.publish(TYPE_C, 'a brief history of time');
const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, res => {
console.log('subscriberA received', res)
});
const subscriberB = new Subscriber('subscriberB', pubsub);
subscriberB.subscribe(TYPE_C, res => {
console.log('subscriberB received', res)
});
const subscriberC = new Subscriber('subscriberC', pubsub);
subscriberC.subscribe(TYPE_B, res => {
console.log('subscriberC received', res)
});
pubsub.notify(TYPE_A);
pubsub.notify(TYPE_B);
pubsub.notify(TYPE_C);
灵感来源于:addEventListener DOM2事件绑定
应用场景:凡是某个阶段到达的时候,需要执行很多方法「更多时候,到底执行多少个方法不确定,需要编写业务边处理的」,我们都可以基于发布订阅设计模式来管理代码;创建事件池->发布计划 向事件池中加入方法->向计划表中订阅任务 fire->通知计划表中的任务执行
let sub = (function () {
let pond = {};
// 向事件池中追加指定自定义事件类型的方法
const on = function on(type, func) {
// 每一次增加的时候,验证当前类型在事件池中是否已经存在
!Array.isArray(pond[type]) ? pond[type] = [] : null;
let arr = pond[type];
if (arr.includes(func)) return;
arr.push(func);
};
// 从事件池中移除指定自定义事件类型的方法
const off = function off(type, func) {
let arr = pond[type],
i = 0,
item = null;
if (!Array.isArray(arr)) throw new TypeError(`${type} 自定义事件在事件池中并不存在!`);
for (; i < arr.length; i++) {
item = arr[i];
if (item === func) {
// 移除掉
// arr.splice(i, 1); //这样导致数据塌陷
arr[i] = null; //这样只是让集合中当前项值变为null,但是集合的机构是不发生改变的「索引不变」;下一次执行emit的时候,遇到当前项是null,我们再去把其移除掉即可;
break;
}
}
};
// 通知事件池中指定自定义事件类型的方法执行
const emit = function emit(type, ...params) {
let arr = pond[type],
i = 0,
item = null;
if (!Array.isArray(arr)) throw new TypeError(`${type} 自定义事件在事件池中并不存在!`);
for (; i < arr.length; i++) {
item = arr[i];
if (typeof item === "function") {
item(...params);
continue;
}
//不是函数的值都移除掉即可,自己控制i的值
arr.splice(i, 1);
i--;
}
};
return {
on,
off,
emit
};
})();
const fn1 = () => console.log(1);
const fn2 = () => console.log(2);
const fn3 = () => {
console.log(3);
sub.off('A', fn1);
sub.off('A', fn2);
};
const fn4 = () => console.log(4);
const fn5 = () => console.log(5);
const fn6 = () => console.log(6);
sub.on('A', fn1);
sub.on('A', fn2);
sub.on('A', fn3);
sub.on('A', fn4);
sub.on('A', fn5);
sub.on('A', fn6);
setTimeout(() => {
sub.emit('A');
}, 1000);
setTimeout(() => {
sub.emit('A');
}, 2000);
观察者模式:某公司给自己员工发月饼发粽子,是由公司的行政部门发送的,这件事不适合交给第三方,原因是“公司”和“员工”是一个整体
发布-订阅模式:某公司要给其他人发各种快递,因为“公司”和“其他人”是独立的,其唯一的桥梁是“快递”,所以这件事适合交给第三方快递公司解决
上述过程中,如果公司自己去管理快递的配送,那公司就会变成一个快递公司,业务繁杂难以管理,影响公司自身的主营业务,因此使用何种模式需要考虑什么情况两者是需要耦合的
在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)