Appearance
JavaScript 设计模式指南
设计模式优先级排序
1. 单例模式
核心思想
确保一个类只有一个实例,并提供一个全局访问点。
优点
- 确保全局只有一个实例,避免资源浪费
- 提供统一的访问点,方便管理
- 可以延迟初始化,提高性能
缺点
- 违反单一职责原则,同时负责创建和管理实例
- 可能导致模块间的紧耦合
- 测试困难,因为单例是全局状态
使用场景
- 全局配置对象
- 数据库连接池
- 日志记录器
- 缓存
代码示例
javascript
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance
}
this.data = 'Singleton instance'
Singleton.instance = this
return this
}
getData() {
return this.data
}
}
// 使用
const instance1 = new Singleton()
const instance2 = new Singleton()
console.log(instance1 === instance2) // true2. 工厂模式
核心思想
通过工厂方法创建对象,而不是直接使用构造函数。
优点
- 封装对象创建过程,隐藏实现细节
- 便于统一管理对象创建
- 支持多态,根据条件创建不同类型的对象
缺点
- 增加了代码复杂度
- 可能导致类的数量增加
使用场景
- 复杂对象的创建
- 需要根据不同条件创建不同类型对象的场景
- 框架和库的设计
代码示例
javascript
class Product {
constructor(name) {
this.name = name
}
getInfo() {
return `Product: ${this.name}`
}
}
class ProductFactory {
static createProduct(type) {
switch (type) {
case 'electronics':
return new Product('Electronics')
case 'clothing':
return new Product('Clothing')
case 'food':
return new Product('Food')
default:
throw new Error('Invalid product type')
}
}
}
// 使用
const electronics = ProductFactory.createProduct('electronics')
console.log(electronics.getInfo()) // Product: Electronics3. 观察者模式
核心思想
定义对象间的一种一对多依赖关系,当一个对象状态发生变化时,所有依赖它的对象都得到通知并自动更新。
优点
- 实现松耦合,观察者和被观察者之间没有直接依赖
- 支持广播通信,一个对象的变化可以通知多个对象
- 符合开闭原则,新增观察者无需修改原有代码
缺点
- 如果观察者太多,通知可能会比较耗时
- 可能导致循环依赖
- 观察者只知道被观察者发生了变化,但不知道具体发生了什么变化
使用场景
- 事件处理系统
- 消息通知系统
- 数据绑定
- 发布/订阅系统
代码示例
javascript
class Subject {
constructor() {
this.observers = []
}
subscribe(observer) {
this.observers.push(observer)
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer)
}
notify(data) {
this.observers.forEach(observer => observer.update(data))
}
}
class Observer {
update(data) {
console.log(`Observer received data: ${data}`)
}
}
// 使用
const subject = new Subject()
const observer1 = new Observer()
const observer2 = new Observer()
subject.subscribe(observer1)
subject.subscribe(observer2)
subject.notify('Hello World!')
// Output:
// Observer received data: Hello World!
// Observer received data: Hello World!4. 装饰器模式
核心思想
动态地给对象添加额外的职责,而不修改其原始结构。
优点
- 比继承更灵活,可以动态添加或移除职责
- 遵循单一职责原则,每个装饰器只负责一个功能
- 可以组合多个装饰器,实现复杂功能
缺点
- 可能导致代码结构复杂,难以理解
- 装饰器链可能过长
使用场景
- 功能扩展
- 权限控制
- 日志记录
- 缓存
代码示例
javascript
class Component {
operation() {
return 'Component operation'
}
}
class Decorator {
constructor(component) {
this.component = component
}
operation() {
return this.component.operation()
}
}
class LoggingDecorator extends Decorator {
operation() {
console.log('Logging before operation')
const result = super.operation()
console.log('Logging after operation')
return result
}
}
class CachingDecorator extends Decorator {
constructor(component) {
super(component)
this.cache = {}
}
operation() {
const key = 'operation'
if (!this.cache[key]) {
this.cache[key] = super.operation()
}
return this.cache[key]
}
}
// 使用
const component = new Component()
const loggingComponent = new LoggingDecorator(component)
const cachingComponent = new CachingDecorator(loggingComponent)
console.log(cachingComponent.operation())5. 模块模式
核心思想
使用闭包创建私有变量和方法,只暴露公共接口。
优点
- 实现封装,保护私有变量
- 避免全局命名空间污染
- 支持模块化开发
缺点
- 模块一旦加载,无法动态修改
- 可能导致内存泄漏
使用场景
- 模块化开发
- 库和框架的设计
- 避免全局变量污染
代码示例
javascript
const Module = (function () {
// 私有变量
let privateVar = 0
// 私有方法
function privateMethod() {
return 'Private method'
}
// 公共接口
return {
publicVar: 1,
publicMethod: function () {
privateVar++
return `Public method, privateVar: ${privateVar}, ${privateMethod()}`
}
}
})()
// 使用
console.log(Module.publicVar) // 1
console.log(Module.publicMethod()) // Public method, privateVar: 1, Private method
console.log(Module.privateVar) // undefined6. 代理模式
核心思想
为其他对象提供一种代理以控制对这个对象的访问。
优点
- 可以在访问对象前进行预处理
- 可以控制访问权限
- 可以实现延迟加载
缺点
- 增加了代码复杂度
- 可能导致请求处理速度变慢
使用场景
- 权限控制
- 远程代理
- 虚拟代理(延迟加载)
- 缓存代理
代码示例
javascript
class RealSubject {
request() {
return 'RealSubject request'
}
}
class Proxy {
constructor(realSubject) {
this.realSubject = realSubject
}
request() {
// 预处理
console.log('Proxy: Preprocessing request')
// 调用真实对象
const result = this.realSubject.request()
// 后处理
console.log('Proxy: Postprocessing request')
return result
}
}
// 使用
const realSubject = new RealSubject()
const proxy = new Proxy(realSubject)
console.log(proxy.request())
// Output:
// Proxy: Preprocessing request
// RealSubject request
// Proxy: Postprocessing request7. 命令模式
核心思想
将请求封装为对象,使得可以用不同的请求参数化客户端。
优点
- 解耦发送者和接收者
- 支持命令队列和撤销操作
- 便于扩展新命令
缺点
- 可能导致类的数量增加
- 命令对象可能会变得复杂
使用场景
- 撤销/重做操作
- 队列请求
- 事务处理
- 日志记录
代码示例
javascript
class Command {
execute() {
throw new Error('Subclass must implement execute method')
}
undo() {
throw new Error('Subclass must implement undo method')
}
}
class LightOnCommand extends Command {
constructor(light) {
super()
this.light = light
}
execute() {
this.light.turnOn()
}
undo() {
this.light.turnOff()
}
}
class Light {
turnOn() {
console.log('Light is on')
}
turnOff() {
console.log('Light is off')
}
}
class RemoteControl {
setCommand(command) {
this.command = command
}
pressButton() {
this.command.execute()
}
pressUndo() {
this.command.undo()
}
}
// 使用
const light = new Light()
const lightOnCommand = new LightOnCommand(light)
const remote = new RemoteControl()
remote.setCommand(lightOnCommand)
remote.pressButton() // Light is on
remote.pressUndo() // Light is off8. 策略模式
核心思想
定义一系列算法,把它们封装起来,并且使它们可以互相替换。
优点
- 算法可以独立于使用它的客户端变化
- 避免使用大量的条件语句
- 符合开闭原则
缺点
- 客户端必须了解所有策略
- 策略类可能会变得很多
使用场景
- 不同算法的选择
- 排序算法
- 支付方式选择
- 验证规则
代码示例
javascript
class Strategy {
execute() {
throw new Error('Subclass must implement execute method')
}
}
class AddStrategy extends Strategy {
execute(a, b) {
return a + b
}
}
class SubtractStrategy extends Strategy {
execute(a, b) {
return a - b
}
}
class Context {
constructor(strategy) {
this.strategy = strategy
}
setStrategy(strategy) {
this.strategy = strategy
}
executeStrategy(a, b) {
return this.strategy.execute(a, b)
}
}
// 使用
const context = new Context(new AddStrategy())
console.log(context.executeStrategy(10, 5)) // 15
context.setStrategy(new SubtractStrategy())
console.log(context.executeStrategy(10, 5)) // 59. 适配器模式
核心思想
将一个类的接口转换成客户希望的另一个接口。
优点
- 使不兼容的接口可以一起工作
- 提高代码复用性
- 符合开闭原则
缺点
- 增加了代码复杂度
- 可能会导致性能损失
使用场景
- 集成第三方库
- legacy 代码适配
- 不同系统间的接口转换
代码示例
javascript
// 旧接口
class OldCalculator {
operations(t1, t2, operation) {
switch (operation) {
case 'add':
return t1 + t2
case 'subtract':
return t1 - t2
default:
return NaN
}
}
}
// 新接口
class NewCalculator {
add(t1, t2) {
return t1 + t2
}
subtract(t1, t2) {
return t1 - t2
}
}
// 适配器
class CalculatorAdapter {
constructor() {
this.calculator = new NewCalculator()
}
operations(t1, t2, operation) {
switch (operation) {
case 'add':
return this.calculator.add(t1, t2)
case 'subtract':
return this.calculator.subtract(t1, t2)
default:
return NaN
}
}
}
// 使用
const oldCalc = new OldCalculator()
console.log(oldCalc.operations(10, 5, 'add')) // 15
const adapter = new CalculatorAdapter()
console.log(adapter.operations(10, 5, 'add')) // 1510. 外观模式
核心思想
为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。
优点
- 简化客户端代码
- 减少客户端与子系统之间的依赖
- 提高系统的可维护性
缺点
- 可能会隐藏子系统的复杂性,导致客户端无法充分利用子系统的功能
- 可能会成为系统中的瓶颈
使用场景
- 复杂系统的简化接口
- 库和框架的设计
- 遗留系统的包装
代码示例
javascript
// 子系统
class Subsystem1 {
operation1() {
return 'Subsystem1 operation1'
}
}
class Subsystem2 {
operation2() {
return 'Subsystem2 operation2'
}
}
// 外观
class Facade {
constructor() {
this.subsystem1 = new Subsystem1()
this.subsystem2 = new Subsystem2()
}
operation() {
return `${this.subsystem1.operation1()} + ${this.subsystem2.operation2()}`
}
}
// 使用
const facade = new Facade()
console.log(facade.operation()) // Subsystem1 operation1 + Subsystem2 operation2总结
设计模式是解决软件设计中常见问题的可重用方案。在 JavaScript 开发中,选择合适的设计模式可以:
- 提高代码的可维护性和可扩展性
- 促进代码复用
- 改善代码结构和可读性
- 帮助团队成员之间的沟通
选择设计模式时,应根据具体的业务需求和代码场景,权衡各种模式的优缺点,选择最适合的解决方案。记住,设计模式不是银弹,过度使用或不当使用可能会导致代码变得复杂和难以理解。