几种常用的设计模式
2023-05-13· 10min
#观察者模式(Observer Pattern)
- 令多个观察者(Observer)同时监听某一个主题(Subject), 当主题(Subject)发生改变时, 所有的观察者都会收到通知并更新
- 观察者(Observer)有多个, 被观察的主题(Subject)只有一个
#优缺点
- 优点
- 观察者和被观察者之间, 建立了一个抽象耦合,易于扩展观察者和被观察者
- 支持广播通信
- 缺点
- 若观察者过多,会使触发时间过长
- 若有循环依赖,会导致系统奔溃
#代码实现
class Subject {
constructor() {
this.observers = [];
}
addObserver(...observer) {
this.observers.push(...observer);
}
notifyObservers(...args) {
this.observers.forEach((observer) => observer.getMsg(...args));
}
}
class Observer {
constructor(name) {
this.name = name;
}
getMsg(...args) {
console.log(`${this.name} get a msg: ${args}`);
}
}
var obs1 = new Observer("obs1");
var obs2 = new Observer("obs2");
var sub = new Subject();
sub.addObserver(obs1, obs2);
sub.notifyObservers(`hi~ ${+new Date()}`);
// obs1 get a msg: hi~ 1684207960180
// obs2 get a msg: hi~ 1684207960180
#发布-订阅模式(Publish–Subscribe pattern)
- 是一种消息范式,发布者(Publisher)和订阅者(Subscriber)不用互相知道,发布者通过广播通道(Event Channel)将消息广播出去,订阅者根据自己的订阅得到相应的消息。
- 是一种对象间一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。
- 是一种经过解耦合的观察者模式。
- 和观察者模式的区别:
- 观察者模式:观察者(Observer)知道 主题(Subject) ,主题(Subject)一直保持对观察者进行记录。应用场景多数是同步
- 发布订阅模式:发布者(Publisher)和订阅者(Subscriber)不知道对方的存在,它们只能通过 广播通道 进行通信。应用场景多数是异步
#优缺点
- 优点
- 对象之间解耦
- 异步编程中,代码可以松耦合
- 缺点
- 弱化对象之间的联系,程序难以追踪维护
#代码实现
class PubSub {
constructor() {
this.subscribes = new Map();
}
addSubEv(type, callback, once) {
const sub = this.subscribes.get(type) || [];
sub.push({ fn: callback, once });
this.subscribes.set(type, sub);
}
// 订阅
sub(type, callback) {
this.addSubEv(type, callback);
}
// 订阅一次
subOnce(type, callback) {
this.addSubEv(type, callback, true);
}
// 取消订阅
subCancel(type, callback) {
const sub = this.subscribes.get(type);
if (sub) {
this.subscribes.set(
type,
sub.filter(({ fn }) => fn !== callback),
);
}
}
// 发布
pub(type, ...args) {
const sub = this.subscribes.get(type) || [];
sub.forEach(({ fn }) => fn.call(this, ...args));
const newSub = sub.filter((item) => !item.once);
this.subscribes.set(type, newSub);
}
}
// PS: use var for repeat test with devtool console...
var pubSub = new PubSub();
var EV_TYPE_MAP = {
ev1: 1,
ev2: 2,
};
pubSub.sub(EV_TYPE_MAP.ev1, () => {
console.log("ev1 console");
});
pubSub.sub(EV_TYPE_MAP.ev1, () => {
console.log("ev1 again console");
});
pubSub.pub(EV_TYPE_MAP.ev1); // ev1 console、ev1 again console
pubSub.subCancel(EV_TYPE_MAP.ev1, () => {
console.log("ev1 subCancel console");
});
pubSub.subOnce(EV_TYPE_MAP.ev2, () => {
console.log("ev2 console");
});
pubSub.pub(EV_TYPE_MAP.ev2); // ev2 console...
pubSub.pub(EV_TYPE_MAP.ev2); // nothing
#策略模式(Strategy Pattern)
- 定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换
#优缺点
- 优点
- 对开闭原则的完美支持
- 缺点
- 策略类会增多,客户端必须知道所有的策略类,并理解其区别
#代码实现
var strategy = function (st) {
switch (st) {
case "S1":
return 1;
case "S2":
return 2;
default:
break;
}
};
console.log(strategy("S1"));
#单例模式(Singleton Pattern)
- 确保一个类只有一个实例,并提供一个全局访问点以获取该实例
#优缺点
- 优点
- 提供全局访问点,方便访问
- 避免全局变量污染
- 避免重复创建,减少内存占用
- 缺点
- 没有接口,无法继承
- 违反开闭原则,若类的功能变化,需要修改代码
- 全局状态的滥用会导致代码难以理解和维护
- 会隐藏类之间的依赖关系,难以测试
#场景
- 全局配置、缓存、模态框等(在全局需要共享/复用的场景)
- 全局状态管理(Vuex、Redux...),全局对象(Jquery的$、浏览器中的window、document等)、ES6的模块导出等
#代码实现
- JS 实现
// 通过 IIFE 和 闭包,将SingleClass封装在模块内部,外部无法直接访问,确保了单例的安全性
const SingleDialog = (function () {
let instance;
class SingleClass {
constructor(name) {
this.name = name;
}
logName() {
console.log(`This is SingleDialog: ${this.name}`);
}
}
return {
getInstance(name) {
if (!instance) {
instance = new SingleClass(name);
}
return instance;
},
};
})();
const single1 = SingleDialog.getInstance("Login");
const single2 = SingleDialog.getInstance("Home");
console.log(`single1 === single2: ${single1 === single2}`); // true
single1.logName(); // Login
single2.logName(); // Login
- TS实现
class SingleDialog {
// 静态属性保存唯一实例
private static instance: SingleDialog;
private name: string;
// 私有构造函数,防止外部实例化
private constructor(name: string) {
this.name = name;
}
// 静态方法获取唯一实例
public static getInstance(name: string): SingleDialog {
if (!SingleDialog.instance) {
SingleDialog.instance = new SingleDialog(name);
}
return SingleDialog.instance;
}
public logName(): void {
console.log(`This is SingleDialog name: ${this.name}`);
}
}
const single1 = SingleDialog.getInstance("Login");
const single2 = SingleDialog.getInstance("Home");
console.log(`single1 === single2: ${single1 === single2}`); // true
single1.logName(); // Login
single2.logName(); // Login