开发中常用的设计模式 用法及注意事项【面试题】

server/2025/3/25 22:06:26/

常见的设计模式:单例模式、工厂模式、观察者模式、发布-订阅模式、装饰器模式、策略模式、代理模式、模块模式等
React中的高阶组件(装饰器模式)、Vue的事件总线(发布-订阅模式

一、 单例模式 (Singleton)

用途:确保一个类只有一个实例,并提供全局访问点(如全局状态管理、配置对象)。

javascript">class Logger {constructor() {if (!Logger.instance) {this.logs = [];Logger.instance = this;}return Logger.instance;}log(message) {this.logs.push(message);console.log(message);}
}
const logger1 = new Logger();
const logger2 = new Logger();
console.log(logger1 === logger2); // true

注意事项
避免滥用,过度使用单例可能导致全局状态污染。
在模块化系统中(如 ES6 Modules),直接用 export 导出实例更简洁。

二、 工厂模式 (Factory)

用途:封装对象的创建逻辑,根据输入参数返回不同类的实例(如创建 UI 组件、不同数据源连接)。

javascript">class Button {render() {console.log("Base Button");}
}class PrimaryButton extends Button {render() {console.log("Primary Button");}
}class SecondaryButton extends Button {render() {console.log("Secondary Button");}
}function createButton(type) {switch (type) {case "primary":return new PrimaryButton();case "secondary":return new SecondaryButton();default:return new Button();}
}const btn1 = createButton("primary");
btn1.render(); // "Primary Button"

注意事项
适合对象创建逻辑复杂的场景。
新增类型时需要修改工厂函数,违反开闭原则

三、 观察者模式 (Observer)

用途:定义对象间的一对多依赖关系,当一个对象状态变化时,自动通知所有依赖者(如事件处理、数据绑定)。

javascript">class Subject {constructor() {this.observers = [];}addObserver(observer) {this.observers.push(observer);}notify(data) {this.observers.forEach(observer => observer.update(data));}
}class Observer {update(data) {console.log("Received data:", data);}
}const subject = new Subject();
const observer1 = new Observer();
subject.addObserver(observer1);
subject.notify("Hello!"); // "Received data: Hello!"

注意事项
观察者和被观察者可能形成循环依赖。
如果没有正确移除观察者会导致内存泄漏

四、 发布-订阅模式 (Pub-Sub)

用途:解耦事件的发布者和订阅者,通过事件中心管理通信(如 Vue 的事件总线、Redux 的订阅机制)。

javascript">class EventBus {constructor() {this.events = {};}subscribe(eventName, callback) {if (!this.events[eventName]) {this.events[eventName] = [];}this.events[eventName].push(callback);}publish(eventName, data) {if (this.events[eventName]) {this.events[eventName].forEach(cb => cb(data));}}
}const bus = new EventBus();
bus.subscribe("message", data => console.log("Received:", data));
bus.publish("message", "Hello World!"); // "Received: Hello World!"

注意事项
比观察者模式更解耦,但事件命名冲突可能导致问题。
避免过度使用,高频事件可能影响性能

五、 装饰器模式 (Decorator)

用途:动态扩展对象功能,不修改原对象代码(如日志记录、权限校验)。
代码示例(使用 ES7 装饰器语法):

javascript">function log(target, name, descriptor) {const original = descriptor.value;descriptor.value = function (...args) {console.log(`Calling ${name} with args:`, args);return original.apply(this, args);};return descriptor;
}class Calculator {@logadd(a, b) {return a + b;}
}const calc = new Calculator();
calc.add(2, 3); // 输出: "Calling add with args: [2, 3]"

注意事项
装饰器可能增加代码复杂度。
JavaScript 原生装饰器语法需要 Babel 编译支持

六、 策略模式 (Strategy)

用途:定义一系列算法,使其可互相替换(如表单验证、支付方式选择)。

javascript">
const strategies = {add: (a, b) => a + b,subtract: (a, b) => a - b,multiply: (a, b) => a * b
};class Calculator {calculate(strategy, a, b) {return strategies[strategy](a, b);}
}const calc = new Calculator();
console.log(calc.calculate("add", 5, 3)); // 8

注意事项
适合替换大量条件分支语句。
如果策略对象可能过多,需合理管理

七、代理模式 (Proxy)

用途:为对象提供代理以控制访问(如数据校验、缓存、延迟加载)。
代码示例(使用 ES6 Proxy):

javascript">
const user = {name: "John",age: 30
};const proxy = new Proxy(user, {get(target, prop) {console.log(`Reading ${prop}`);return target[prop];},set(target, prop, value) {if (prop === "age" && value < 0) {throw new Error("Invalid age");}target[prop] = value;return true;}
});console.log(proxy.name); // "Reading name" → "John"
proxy.age = -1; // 抛出错误

注意事项
可能引入性能开销。
避免过度拦截导致调试困难

八、 模块模式 (Module)

用途:封装私有变量和方法,暴露公有接口(如工具库、组件封装)。

javascript">
const CounterModule = (() => {let count = 0;const increment = () => {count++;console.log("Count:", count);};return {increment};
})();CounterModule.increment(); // "Count: 1"
// CounterModule.count → 无法访问

注意事项
适合小型模块化开发。
现代开发中更推荐使用 ES6 Modules

总结

单例模式: 用于全局状态管理、配置共享,注意避免全局污染
工厂模式: 用于动态创建对象,注意新增类型需修改工厂函数
观察者/发布订阅模式: 用于事件驱动、数据绑定,注意防止内存泄漏、命名冲突
装饰器模式: 用于功能扩展(AOP 编程),注意增加代码复杂度
策略模式: 用于替换条件分支,注意管理大量策略对象
代理模式: 用于访问控制、缓存,注意性能开销
模块模式: 用于封装私有逻辑,注意现代开发优先使用 ES6 Modules

设计原则
不要过度设计:仅在复杂度需要时引入设计模式
优先使用语言特性:如 ES6 的 Proxy、class、模块等原生支持。


http://www.ppmy.cn/server/179072.html

相关文章

[React 进阶系列] 组合组件 复合组件

[React 进阶系列] 组合组件 & 复合组件 今天写个人项目练手的时候搜到了一个比价有趣的实现&#xff0c;于是用了一下&#xff0c;发现这个 concept 不是特别的熟&#xff0c;于是上网找了下&#xff0c;返现了一个叫 复合组件(compound components) 的概念。搜索了一下后…

RISC-V: 固件与操作系统引导 | eg OpenSBI | 借助AI注释项目代码

引入&#xff1a;计算机没有黑魔法 例如我们都可以&#xff0c;通过指令来查看我们计算机的信息 “Everything is a State Machine” 在许多状态之间不断切换程序就运行了起来Makefile 也是程序&#xff1b;它也是状态机程序不好读的话&#xff0c;我们还可以调试它&#xff0…

Language Models are Few-Shot Learners,GPT-3详细讲解

GPT的训练范式&#xff1a;预训练Fine-Tuning GPT2的训练范式&#xff1a;预训练Prompt predict &#xff08;zero-shot learning&#xff09; GPT3的训练范式&#xff1a;预训练Prompt predict &#xff08;few-shot learning&#xff09; GPT2的性能太差&#xff0c;新意高&…

国产开发板—米尔全志T113-i如何实现ARM+RISC-V+DSP协同计算?

近年来&#xff0c;随着半导体产业的快速发展和技术的不断迭代&#xff0c;物联网设备种类繁多&#xff08;如智能家居、工业传感器&#xff09;&#xff0c;对算力、功耗、实时性要求差异大&#xff0c;单一架构无法满足所有需求。因此米尔推出MYD-YT113i开发板&#xff08;基…

Unity音频混合器如何暴露参数

音频混合器是Unity推荐管理音效混音的工具&#xff0c;那么如何使用代码对它进行管理呢&#xff1f; 首先我在AudioMixer的Master组中创建了BGM和SFX的分组&#xff0c;你也可以直接用Master没有问题。 这里我以BGM为例&#xff0c;如果要在代码中进行使用就需要将参数暴露出去…

linux去掉绝对路径前面部分和最后的/符号

使用basename命令 basename命令用于获取路径中的文件名部分。它会自动去除路径前面的目录部分和最后的/符号。示例如下&#xff1a; path"/a/b/c" filename$(basename "$path") echo "$filename"path"/a/b/c/" filename$(basename &…

【测试工具】如何使用 burp pro 自定义一个拦截器插件

在 Burp Suite 中&#xff0c;你可以使用 Burp Extender 编写自定义拦截器插件&#xff0c;以拦截并修改 HTTP 请求或响应。Burp Suite 支持 Java 和 Python (Jython) 作为扩展开发语言。以下是一个完整的流程&#xff0c;介绍如何创建一个 Burp 插件来拦截请求并进行自定义处理…

Spring-Mybatis框架常见面试题

1、介绍下什么是Spring框架的IOC和DI IOC 控制反转&#xff0c;指将对象的创建权&#xff0c;反转到Spring容器&#xff1b; DI 依赖注入&#xff0c;指Spring创建对象的过程中&#xff0c;将对象依赖属性通过配置进行注入,不能单独存在&#xff0c;需要在IOC的基础上完成操作…