一、单例模式
概念总结:保证一个类仅有一个实例,并提供一个访问它的全局访问点
class Singleton {
// 私有构造函数
constructor() {
// 实例为空时,新建实例
if (!Singleton.instance) {
this.name = 'Singleton';
}
}
// 公有获取实例方法
static getInstance() {
// 若实例为空,新建实例
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
// 返回实例
return Singleton.instance;
}
// 实例方法
showName() {
console.log(this.name); // Singleton
}
}
// 获取实例
let instance1 = Singleton.getInstance();
// 获取实例
let instance2 = Singleton.getInstance();
// 打印实例1和实例2
console.log(instance1 === instance2); // true
// 调用实例方法
instance1.showName(); // Singleton
上述代码实现了单例模式的类写法,关键在于:
- 私有构造函数,限制外部直接 new
- 公有静态方法 getInstance 获取实例
- 在 getInstance 方法中判断实例是否存在,不存在则新建
- getInstance 每次都返回同一个实例
- 这样确保了整个程序中只有一个实例存在
二、观察者模式
概念总结:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
// 发布者类
class Publisher {
constructor() {
// 存储订阅者列表
this.observers = [];
}
// 添加订阅者
add(observer) {
// 将订阅者添加到列表
this.observers.push(observer);
}
// 删除订阅者
remove(observer) {
// 从订阅者列表中删除订阅者
this.observers = this.observers.filter((o) => o !== observer);
}
// 通知订阅者
notify() {
// 遍历订阅者列表,调用每个订阅者的update方法
this.observers.forEach((o) => o.update());
}
}
// 订阅者类
class Observer {
// 构造函数接收订阅者名称
constructor(name) {
this.name = name;
}
// 取消订阅
unsubscribe() {
// 从发布者的订阅者列表中删除自己
pub.remove(this);
}
// 更新方法,订阅发布者通知后被调用
update() {
console.log(`已通知:${this.name} 告退!`);
}
}
// 新的发布者实例
const pub = new Publisher();
// 两个订阅者实例
const obs1 = new Observer('订阅者一号:obs1');
const obs2 = new Observer('订阅者二号:codewhite');
// 将两个订阅者添加到发布者
pub.add(obs1);
pub.add(obs2);
// 删除订阅者obs1
obs1.unsubscribe();
// 发布者通知订阅者
pub.notify(); // 已通知:订阅者二号:codewhite 告退!
我添加了详细注释帮助理解这段代码的实现原理。它包含以下:
- 发布者:添加、删除订阅者和通知订阅者
- 订阅者:取消订阅和更新方法
- 演示了如何删除订阅者obs1,之后只有obs2接收到了通知
三、代理模式
概念总结:为另一个对象提供一个代理或者占位符,并控制对这个对象的访问。代理控制对目标对象的访问,并可以执行相关操作,比如延迟加载、访问控制、缓存等
// 目标对象
const target = {
// 目标方法
doSomething: function () {
console.log('Target: doSomething 目标方法');
},
};
// 代理类
class Proxy {
constructor() {
// 维护对目标对象的引用
this.target = target;
}
// 代理方法
doSomething() {
console.log('Proxy: doSomething 代理方法');
// 调用目标对象方法
this.target.doSomething();
}
}
// 代理对象
const proxy = new Proxy();
// 通过代理对象调用方法
proxy.doSomething();
// Proxy: doSomething 代理方法
// Target: doSomething 目标方法
上述代码实现了代理模式,主要包含:
目标对象:需要代理的目标方法代理类:维护对目标对象的引用并提供代理方法代理方法:预处理操作,调用目标方法,后续操作- 通过代理对象调用方法实际执行的是代理方法
- 代理方法负责在目标方法前后执行需要的操作,并中转调用目标方法
- 代理模式的关键在于代理类代理了目标对象,通过代理对象调用方法实现了对目标对象方法的间接访问,并扩展了额外功能
四、工厂模式
概念总结:工厂模式解决的主要问题是实例化对象。它提供一种机制,可以让子类决定应当创建哪个类的实例
工厂模式有以下主要特征:
- 工厂类:用于创建对象的类,包含判断逻辑和实例化对象的方法
- 产品类:需要被工厂类实例化的类,包含实例方法
- 创建方法:工厂类中的方法,根据传入的参数创建对应的产品类实例
- 调用方法:通过工厂类的创建方法实例化产品类,然后调用产品类实例的方法
工厂模式的主要优点是:
- 隐藏对象创建的复杂性。调用者只需要传入正确的参数,工厂方法会返回正确的对象
- 新加入的产品类不会影响已有的调用方法。我们只需要在工厂类中增加判断逻辑和对象创建代码即可
- 更容易扩展产品类。我们可以增加新的产品类,并在工厂方法中进行创建,而不影响已有的调用方法
实现方式主要有简单工厂和工厂方法两种模式:
- 简单工厂:工厂类包含判断逻辑和所有产品类的实例化方法
- 工厂方法:将产品类实例化推迟到子类。工厂类中只包含抽象创建方法,子类实现并实例化相关产品类
综上所述,工厂模式通过引入工厂类和创建方法,实现了实例化对象的解耦。调用者只需传入正确的参数,工厂方法会返回对应的对象实例,屏蔽对象创建的细节与复杂性
简单工厂模式
// 工厂类
class Factory {
// 简单工厂模式
static create(type) {
// 判断类型并实例化对象
if (type === 'A') {
return new ProductA();
} else if (type === 'B') {
return new ProductB();
}
}
}
// 产品A
class ProductA {
doSomething() {
console.log('ProductA');
}
}
// 产品B
class ProductB {
doSomething() {
console.log('ProductB');
}
}
// 调用工厂方法,得到产品A实例并调用方法
const product = Factory.create('A');
product.doSomething(); // ProductA
工厂方法
// 工厂类
class Factory {
// 工厂方法
static create(type) {
// 子类实现
}
}
// 具体工厂1
class ConcreteFactory1 extends Factory {
static create(type) {
// 实例化产品1
if (type === 'product1') {
return new Product1();
}
}
}
// 具体工厂2
class ConcreteFactory2 extends Factory {
static create(type) {
// 实例化产品2
if (type === 'product2') {
return new Product2();
}
}
}
// 产品1
class Product1 {
doSomething() {
console.log('Product1');
}
}
// 产品2
class Product2 {
doSomething() {
console.log('Product2');
}
}
// 调用第一个工厂方法得到产品1实例
const product = ConcreteFactory1.create('product1');
product.doSomething(); // Product1
五、装饰器模式
概念总结:具体装饰对象通过调用父类方法,间接执行了源对象方法
// 源对象接口
class Source {
doSomething() {}
}
// 源对象实现
class ConcreteSource extends Source {
doSomething() {
console.log('Source doSomething');
}
}
// 装饰对象接口
class Decorator {
// 构造函数接收源对象
constructor(source) {
this.source = source;
}
doSomething() {
// 直接调用源对象方法
this.source.doSomething();
}
}
// 具体装饰对象
class ConcreteDecorator extends Decorator {
doSomething() {
console.log('Decorator doSomething');
// 调用父类的方法,间接调用源对象方法
super.doSomething();
}
}
// 源对象实例
const source = new ConcreteSource();
// 装饰对象实例,接收源对象
const decorator = new ConcreteDecorator(source);
// 通过装饰对象调用方法
decorator.doSomething();
// Decorator doSomething
// Source doSomething
在这个实现中:
- 装饰对象接口中,我添加了直接调用源对象方法的代码
- 具体装饰对象通过调用父类方法,间接执行了源对象方法
- 这样,我们实现了装饰对象 decorator 完整地装饰了源对象 source
这是装饰者模式完整的实现,需要同时具有:
- 直接访问源对象方法
- 在此基础上提供额外的装饰与扩展
六、命令模式
// 命令接口
class Command {
execute() {}
}
// 具体命令1
class ConcreteCommand1 extends Command {
// 构造函数接收命令执行函数
constructor(func) {
super();
this.func = func;
}
execute() {
// 执行命令函数
this.func();
}
}
// 具体命令2
class ConcreteCommand2 extends Command {
constructor(func) {
super();
this.func = func;
}
execute() {
this.func();
}
}
// 执行命令1
const executeFunc1 = function () {
console.log('Execute Command1');
};
// 生成命令1
var command1 = new ConcreteCommand1(executeFunc1);
// 执行命令2
const executeFunc2 = function () {
console.log('Execute Command2');
};
// 生成命令2
const command2 = new ConcreteCommand2(executeFunc2);
// 使用命令1
command1.execute(); // Execute Command1
// 使用命令2
command2.execute(); // Execute Command2
这个实现包含:
- 命令接口:定义执行方法
- 具体命令:实现命令接口,构造函数接收真实命令函数
- 执行函数:需要被封装成命令的函数
- 生成命令:通过具体命令传入执行函数,生成命令对象
- 执行命令:调用命令对象的 execute 方法,间接执行真实函数
命令模式的关键在于命令对象封装了真实需要执行的函数。通过调用命令对象的方法,实现了对函数的间接调用。这使得调用者和真实命令执行者解耦
写在最后:
以上内容,便是这次记录分享的几种设计模式,通过简单案例可以加深对这几种设计模式的记忆和理解,当然,在我们开发中,设计模式更是无处不在,观察订阅者模式等等都是老朋友了,日久积累时非常重要的持久化输出手段,加油!各位同学~