我们要了解设计模式之前,先要了解一下设计原则。
大概分为五大原则(SOLID):
- 单一职责原则(Single Responsibility Principle)
一个程序就做好一件事,尽量纯一些
- 开放封闭原则(Open Close Principle)
对扩展开放,对修改封闭
- 里氏替换原则(Liskov Substitution Principle)
子类能覆盖父类,所有父类能出现的地方,子类都能出现
- 接口隔离原则(InterfaceSegregation Principles)
保持接口独立单一,一个接口不要包揽所有事
- 依赖倒置原则(Dependence Inversion Principle)
面向接口编程,依赖于抽象而不依赖于具体
所谓的设计模式,也是经过岁月打磨之后。总结出来的遵循这五大设计原则的,一套行之有效的可以复制的解决某些问题的模板套路。如果在程序设计和程序编写的时候,咱能够时刻注意遵循这五大原则,甚至不用学习23种具体的设计模式套路。说不定咱也能写出来优质代码,只是不知道这是一种模式罢了。
今天先讲观察者模式/发布订阅模式,为什么第一篇是这个模式呢?
因为对于前端来说,这是极其重要的。换句话说,如果只会一种设计模式,那应该就是观察者模式了。
有的资料上会区分观察者模式和发布订阅模式,尤其是面试的时候。感觉没有必要。记那么多干啥,是吧。都是一对一或者一对多的模式。就是说:一个程序,通知其他程序(一个或者多个)去干活。就这么个事。只是实现方式上有稍微的区别而已,根据具体的业务场景去调节就可以了。
观察者模式是有一个主题,平常可能有自己的任务,同时保存了状态和一个观察者列表,如果某个状态发生变化的时候就通知所有观察者去干活。
// 主题 保存状态,和保存所有的观察者 状态变化之后 通知 所有的观察者
class Subject {
constructor() {
this.state = 0;
this.observers = [];
}
setState(val) {
this.state = val;
this.notifyAllObaservers(val); // 状态改变 通知所有的观察者,也可以将新值传进去,根据新值操作
}
notifyAllObaservers(val) { // 状态改变 通知所有的观察者
// 所有的观察者执行自己的update方法,就是干自己的活了
this.observers.forEach(observer => observer.update(val));
}
addObserver(observer) { // 添加新的观察者
this.observers.push(observer);
}
}
// 观察者
class Observer {
constructor(name, subject) {
// 在new观察者的时候 就要将该观察者 放入到观察者列表中去
this.name = name;
subject.addObserver(this); // 将该观察者 放入到观察者列表中去
}
update(val) {
console.log(`${this.name} 更新了, 最新状态值为: ${val}`)
}
}
// 测试
const subject = new Subject(); // 创建主题
const observer1 = new Observer('observer1', subject); // 创建观察者
const observer2 = new Observer('observer2', subject);
const observer3 = new Observer('observer3', subject);
subject.setState(1);
subject.setState(2);
subject.setState(3);
发布订阅模式,只不过是多了一个调度中心。主题不再直接通知观察者去干活了。而是主题通知调度中心,由调度中心去通知观察者去干活。这种耦合度更低。适合主题不能直接跟观察者通信的时候,或者不适合直接通信的时候。订阅者可以自定义订阅哪些消息。Vue中的时间总线就是发布订阅者模式。本质还是一个程序,通知其他程序(一个或者多个)去干活。
// 调度中心 维护 订阅消息和订阅者列表
class DispatchCenter {
constructor() {
this.subjects = {} // 初始 保存订阅消息和该订阅消息下的函数集合
}
// 订阅 消息
on(subscriberMsg, fn) {
if (!this.subjects[subscriberMsg]) {
this.subjects[subscriberMsg] = [];
}
this.subjects[subscriberMsg].push(fn);
};
// 解除订阅
off(subscriberMsg, fn) {
if (!fn) { // 如果不传 就全部解除 该消息下的所有绑定事件
this.subjects[subscriberMsg] = [];
} else {
const index = this.subjects[subscriberMsg].indexOf(fn);
index >= 0 && this.subjects[subscriberMsg].splice(index, 1);
}
};
// 发布者 发布消息
emit(subscriberMsg, ...data) {
this.subjects[subscriberMsg].forEach(item => item(...data));
};
}
// 发布者
class Publisher {
constructor(subscriber, data) {
this.subscriber = subscriber;
this.data = data;
}
}
// 订阅者
class Subscriber {
constructor(subscriber, fn) {
this.subscriber = subscriber;
this.fn = fn;
}
}
const subscriberOne = new Subscriber(
"1号订阅信息",
(...args) => {
console.log("subscriberOne的回调", ...args);
}
);
const subscriberTwo = new Subscriber(
"测试发布信息",
(...args) => {
console.log("subscriberTwo的回调", ...args);
}
);
const publs = new Publisher(
"测试发布信息",
"携带参数1"
);
const eventBus = new DispatchCenter(); // eventBus 熟不熟悉 这个名字
// 订阅者1,2号订阅各自消息
eventBus.on(subscriberOne.subscriber, subscriberOne.fn);
eventBus.on(subscriberTwo.subscriber, subscriberTwo.fn);
// 发布者 发布 "测试发布信息"相关的事件
// eventBus.off ("测试发布信息", subscriberTwo.fn);
eventBus.emit(publs.subscriber, publs.data, "其他参数");
eventBus.emit("1号订阅信息", publs.data, "另外的参数");