设计模式
约 2582 字大约 9 分钟
2025-02-05
问题
常见设计模式有哪些?
单例模式
定义
单例模式 是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。
使用场景
- 配置管理:在应用程序中,某些配置信息(如数据库连接配置、系统全局参数等)只需要一个实例来管理,使用单例模式可以避免多个实例导致的配置不一致问题。
- 日志记录器:日志记录器通常在整个应用程序中只需要一个实例,以确保日志的一致性和可管理性。
代码示例
// 定义单例类
class Singleton {
// 构造函数,用于初始化单例实例
constructor() {
// 检查类的静态属性 instance 是否已经存在实例
if (!Singleton.instance) {
// 如果不存在实例,初始化一个存储数据的数组
this.data = [];
// 将当前实例赋值给类的静态属性 instance
Singleton.instance = this;
}
// 返回类的静态属性 instance 存储的实例
return Singleton.instance;
}
// 向单例实例的数据数组中添加一个元素的方法
addItem(item) {
this.data.push(item);
}
// 获取单例实例的数据数组的方法
getItems() {
return this.data;
}
}
// 创建第一个单例实例
const singleton1 = new Singleton();
// 创建第二个单例实例
const singleton2 = new Singleton();
// 验证两个实例是否为同一个实例,输出 true 则说明单例模式生效
console.log(singleton1 === singleton2);
工厂模式
定义
工厂模式 是一种创建型设计模式,它定义一个创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。
使用场景
- 对象创建过程复杂:当对象的创建过程涉及到多个步骤或依赖时,使用工厂模式可以将创建逻辑封装在工厂类中,提高代码的可维护性。
- 根据不同条件创建对象:根据不同的参数或条件创建不同类型的对象,例如根据用户的选择创建不同类型的图形对象。
代码示例
// 定义形状工厂类,用于创建不同类型的形状对象
class ShapeFactory {
// 根据传入的形状类型创建对应形状对象的方法
createShape(type) {
// 使用 switch 语句根据不同的形状类型进行不同的处理
switch (type) {
// 如果类型是 'circle'
case 'circle':
// 创建并返回一个 Circle 类的实例
return new Circle();
// 如果类型是 'square'
case 'square':
// 创建并返回一个 Square 类的实例
return new Square();
// 如果传入的类型不匹配以上任何一种情况
default:
// 返回 null 表示无法创建对应形状对象
return null;
}
}
}
// 定义圆形类,代表圆形对象
class Circle {
// 绘制圆形的方法,输出相应信息到控制台
draw() {
console.log('Drawing a circle');
}
}
// 定义正方形类,代表正方形对象
class Square {
// 绘制正方形的方法,输出相应信息到控制台
draw() {
console.log('Drawing a square');
}
}
// 创建一个形状工厂的实例
const factory = new ShapeFactory();
// 使用工厂实例创建一个圆形对象
const circle = factory.createShape('circle');
// 调用圆形对象的 draw 方法绘制圆形
circle.draw();
策略模式
定义
策略模式 是一种行为型设计模式,它定义了一系列的算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
使用场景
- 多种算法实现:当一个问题有多种不同的算法实现时,可以使用策略模式将这些算法封装成不同的策略类,客户端可以根据需要选择合适的策略。
- 避免使用大量条件语句:使用策略模式可以避免在代码中使用大量的
if-else
或switch
语句,提高代码的可维护性和可扩展性。
代码示例
// 定义正常收费策略类,代表不打折的收费算法
class CashNormal {
// 接受金额并返回实际收费金额的方法,正常收费不做处理,直接返回原金额
acceptCash(money) {
return money;
}
}
// 定义打折收费策略类,代表有折扣的收费算法
class CashRebate {
// 构造函数,接收折扣率作为参数并保存
constructor(discount) {
this.discount = discount;
}
// 接受金额并返回打折后收费金额的方法,根据折扣率计算实际收费金额
acceptCash(money) {
return money * this.discount;
}
}
// 定义收费上下文类,用于管理和使用不同的收费策略
class CashContext {
// 构造函数,接收一个收费策略实例并保存
constructor(strategy) {
this.strategy = strategy;
}
// 根据传入的金额,调用保存的收费策略的 acceptCash 方法计算实际收费金额并返回
getResult(money) {
return this.strategy.acceptCash(money);
}
}
// 创建一个正常收费策略的实例
const normal = new CashNormal();
// 创建一个使用正常收费策略的收费上下文实例
const context1 = new CashContext(normal);
// 调用收费上下文实例的 getResult 方法计算 100 元的实际收费金额并输出
console.log(context1.getResult(100));
// 创建一个折扣率为 0.8 的打折收费策略的实例
const rebate = new CashRebate(0.8);
// 创建一个使用打折收费策略的收费上下文实例
const context2 = new CashContext(rebate);
// 调用收费上下文实例的 getResult 方法计算 100 元打折后的实际收费金额并输出
console.log(context2.getResult(100));
装饰者模式
定义
装饰者模式 是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
使用场景
- 动态添加功能:当需要在不修改现有对象的基础上,动态地为对象添加新的功能时,可以使用装饰者模式。
- 组合多个功能:可以将多个装饰者组合在一起,为对象添加多个不同的功能。
代码示例
// 定义咖啡类,代表基础的咖啡对象,有获取价格的方法
class Coffee {
// 获取咖啡基础价格的方法,返回价格 5
cost() {
return 5;
}
}
// 定义牛奶装饰者类,用于为咖啡添加牛奶功能
class MilkDecorator {
// 构造函数,接收一个咖啡对象并保存
constructor(coffee) {
this.coffee = coffee;
}
// 获取添加牛奶后咖啡价格的方法,在原咖啡价格基础上加 2
cost() {
return this.coffee.cost() + 2;
}
}
// 定义糖装饰者类,用于为咖啡添加糖功能
class SugarDecorator {
// 构造函数,接收一个咖啡对象并保存
constructor(coffee) {
this.coffee = coffee;
}
// 获取添加糖后咖啡价格的方法,在原咖啡价格基础上加 1
cost() {
return this.coffee.cost() + 1;
}
}
// 创建一个基础咖啡对象
const coffee = new Coffee();
// 创建一个添加了牛奶的咖啡对象,使用牛奶装饰者包装基础咖啡对象
const coffeeWithMilk = new MilkDecorator(coffee);
// 创建一个既添加了牛奶又添加了糖的咖啡对象,使用糖装饰者包装添加了牛奶的咖啡对象
const coffeeWithMilkAndSugar = new SugarDecorator(coffeeWithMilk);
// 输出既添加了牛奶又添加了糖的咖啡的最终价格
console.log(coffeeWithMilkAndSugar.cost());
观察者模式
定义
观察者模式 是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的状态。
使用场景
- 事件处理系统:在图形用户界面(GUI)开发中,当一个组件的状态发生变化时,需要通知其他相关组件进行更新,这时可以使用观察者模式。
- 状态管理:在应用程序中,当某个对象的状态发生变化时,需要通知其他对象进行相应的处理,例如购物车中商品数量的变化需要通知总价的更新。
代码示例
// 定义主题类,代表被观察的对象
class Subject {
// 构造函数,初始化一个空的观察者数组,用于存储所有的观察者对象
constructor() {
this.observers = [];
}
// 向观察者数组中添加一个观察者对象的方法
addObserver(observer) {
this.observers.push(observer);
}
// 从观察者数组中移除一个观察者对象的方法
removeObserver(observer) {
// 查找观察者对象在数组中的索引
const index = this.observers.indexOf(observer);
// 如果找到该观察者对象
if (index!== -1) {
// 从数组中移除该观察者对象
this.observers.splice(index, 1);
}
}
// 通知所有观察者对象状态更新的方法
notifyObservers() {
// 遍历观察者数组,调用每个观察者对象的 update 方法
this.observers.forEach(observer => observer.update());
}
}
// 定义观察者类,代表监听主题对象的观察者
class Observer {
// 构造函数,接收一个观察者名称并保存
constructor(name) {
this.name = name;
}
// 当接收到主题对象的通知时执行的更新方法,输出相应信息到控制台
update() {
console.log(`${this.name} has been notified.`);
}
}
// 创建一个主题对象的实例
const subject = new Subject();
// 创建一个名为 'Observer 1' 的观察者对象实例
const observer1 = new Observer('Observer 1');
// 创建一个名为 'Observer 2' 的观察者对象实例
const observer2 = new Observer('Observer 2');
// 将观察者 1 添加到主题对象的观察者数组中
subject.addObserver(observer1);
// 将观察者 2 添加到主题对象的观察者数组中
subject.addObserver(observer2);
// 主题对象通知所有观察者对象状态更新
subject.notifyObservers();
总结
这几种设计模式各有特点和适用场景,单例模式保证了对象的唯一性,工厂模式将对象的创建和使用分离,策略模式提供了算法的可替换性, 装饰者模式可以动态地为对象添加功能,观察者模式实现了对象之间的一对多依赖关系。