您现在的位置是:网站首页 > 常见设计模式误用与反模式文章详情

常见设计模式误用与反模式

设计模式在JavaScript开发中广泛应用,但误用和反模式同样普遍存在。某些情况下,过度设计或错误选择模式会导致代码复杂度上升、维护困难甚至性能下降。从单例模式的全局污染到观察者模式的内存泄漏,这些问题往往源于对模式核心思想的理解偏差。

单例模式的全局状态污染

单例模式常被误用为全局变量的替代品。虽然单例确实提供全局访问点,但直接暴露可变状态会引发副作用连锁反应:

// 反模式示例:暴露可变状态的单例
const ConfigSingleton = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  setConfig(newConfig) {
    Object.assign(this, newConfig)
  }
}

// 多个模块直接修改全局状态
ConfigSingleton.setConfig({ timeout: 10000 }) // 模块A修改
ConfigSingleton.timeout = 20000 // 模块B直接覆盖

改进方案应封装内部状态并提供受控访问:

// 改进实现:冻结配置并暴露接口
const createConfigSingleton = () => {
  let config = Object.freeze({
    apiUrl: 'https://api.example.com',
    timeout: 5000
  })

  return {
    getConfig: (key) => config[key],
    updateConfig: (newValues) => {
      config = Object.freeze({ ...config, ...newValues })
    }
  }
}

观察者模式的内存泄漏陷阱

未正确注销观察者会导致内存无法回收。常见于DOM事件监听和自定义事件系统:

// 反模式示例:未移除的订阅
class EventBus {
  constructor() {
    this.listeners = {}
  }

  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = []
    }
    this.listeners[event].push(callback)
  }

  emit(event, data) {
    (this.listeners[event] || []).forEach(fn => fn(data))
  }
}

// 组件内订阅
const bus = new EventBus()
function handleClick(data) {
  console.log(data)
}
bus.on('click', handleClick)

// 组件销毁时未取消订阅 -> 内存泄漏

解决方案应强制维护订阅生命周期:

// 改进实现:返回取消订阅函数
class SafeEventBus {
  constructor() {
    this.listeners = new Map()
  }

  on(event, callback) {
    const id = Symbol('eventId')
    const wrapper = { callback, id }

    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set())
    }
    this.listeners.get(event).add(wrapper)

    return () => this.off(event, id)
  }

  off(event, id) {
    const wrappers = this.listeners.get(event)
    if (wrappers) {
      for (const wrapper of wrappers) {
        if (wrapper.id === id) {
          wrappers.delete(wrapper)
          break
        }
      }
    }
  }
}

工厂模式的过度分层问题

不必要的工厂层级会增加代码理解成本。当对象构造逻辑简单时,直接实例化更合适:

// 反模式示例:简单对象的过度工厂化
class UserFactory {
  static createAdmin() {
    return new AdminUser()
  }
  static createGuest() {
    return new GuestUser()
  }
}

// 实际使用
const admin = UserFactory.createAdmin()

// 更直接的替代方案
const admin = new AdminUser()

应保留工厂模式处理复杂构造场景:

// 合理使用工厂:处理依赖组装
class DatabaseConnectionFactory {
  static create(config) {
    const validator = new ConnectionValidator(config)
    const logger = new ConnectionLogger(config)
    const connection = new Connection(config)
    
    connection.setValidator(validator)
    connection.attachLogger(logger)
    
    return connection
  }
}

策略模式的条件爆炸

策略模式可能退化为巨型switch语句,这与模式初衷背道而驰:

// 反模式:策略选择器变成条件地狱
class PaymentProcessor {
  process(paymentMethod, amount) {
    switch (paymentMethod) {
      case 'creditCard':
        return new CreditCardStrategy().execute(amount)
      case 'paypal':
        return new PayPalStrategy().execute(amount)
      case 'crypto':
        return new CryptoStrategy().execute(amount)
      // 更多case...
    }
  }
}

改进方案利用对象字面量实现自动策略发现:

// 改进实现:注册式策略管理
class PaymentProcessor {
  constructor() {
    this.strategies = {}
  }

  registerStrategy(name, strategy) {
    this.strategies[name] = strategy
  }

  process(name, amount) {
    if (this.strategies[name]) {
      return this.strategies[name].execute(amount)
    }
    throw new Error(`Unknown strategy: ${name}`)
  }
}

// 策略注册
processor.registerStrategy('creditCard', new CreditCardStrategy())
processor.registerStrategy('paypal', new PayPalStrategy())

装饰器模式的性能损耗

多层装饰器嵌套可能带来不必要的性能开销,特别是在高频调用的场景:

// 反模式示例:过度装饰的IO操作
class FileReader {
  read(path) {
    // 基础读取逻辑
  }
}

// 装饰器层层嵌套
const reader = new CacheDecorator(
  new LoggingDecorator(
    new ValidationDecorator(
      new FileReader()
    )
  )
)

// 每次read调用需要经过3层代理
reader.read('data.txt')

优化方案应考虑装饰器合并或条件装饰:

// 改进实现:组合装饰逻辑
class OptimizedDecorator {
  constructor(reader) {
    this.reader = reader
    this.cache = new Map()
  }

  read(path) {
    if (this.cache.has(path)) {
      return this.cache.get(path)
    }

    // 组合验证和日志
    if (!validatePath(path)) {
      throw new Error('Invalid path')
    }
    log(`Reading ${path}`)

    const data = this.reader.read(path)
    this.cache.set(path, data)
    return data
  }
}

中介者模式的上帝对象

中介者可能演变为集中所有业务逻辑的上帝对象:

// 反模式:承担过多职责的中介者
class ChatMediator {
  constructor() {
    this.users = []
  }

  register(user) {
    this.users.push(user)
  }

  sendMessage(sender, message) {
    this.users.forEach(user => {
      if (user !== sender) {
        user.receive(message)
      }
    })
  }

  // 不断增长的职责
  kickUser(user) { /*...*/ }
  createRoom() { /*...*/ }
  deleteRoom() { /*...*/ }
  // 更多无关方法...
}

应拆分为多个专注的中介者:

// 改进实现:职责分离
class UserManager {
  constructor() {
    this.users = []
  }
  register(user) { /*...*/ }
  kick(user) { /*...*/ }
}

class MessageBroker {
  broadcast(sender, message) {
    userManager.users.forEach(user => {
      if (user !== sender) user.receive(message)
    })
  }
}

class RoomCoordinator {
  createRoom() { /*...*/ }
  deleteRoom() { /*...*/ }
}

模板方法模式的僵化继承

基类过度控制子类实现会导致灵活性丧失:

// 反模式:强制步骤的模板方法
class DataExporter {
  export() {
    this.validate()
    this.process()
    this.save()
    this.cleanup()
  }

  validate() {
    throw new Error('Must implement validate')
  }
  // 其他抽象方法...
}

// 子类无法跳过不需要的步骤
class CSVExporter extends DataExporter {
  validate() { /*...*/ }
  process() { /*...*/ }
  save() { /*...*/ }
  cleanup() {} // 空实现
}

改用组合模式提供更大灵活性:

// 改进实现:可配置的步骤管道
class DataPipeline {
  constructor(steps = []) {
    this.steps = steps
  }

  addStep(step) {
    this.steps.push(step)
  }

  run() {
    this.steps.forEach(step => step.execute())
  }
}

// 按需组合步骤
const pipeline = new DataPipeline([
  new ValidationStep(),
  new ProcessingStep(),
  new SaveStep()
  // 不需要cleanup则不添加
])

状态模式的状态爆炸

随着状态增长,状态类数量会急剧增加:

// 反模式:每个状态一个类
class Order {
  constructor() {
    this.state = new DraftState(this)
  }

  next() {
    this.state.next()
  }
}

class DraftState { /*...*/ }
class ValidationState { /*...*/ }
class PaymentState { /*...*/ }
class ShippingState { /*...*/ }
class DeliveredState { /*...*/ }
// 更多状态类...

考虑使用状态表简化管理:

// 改进实现:状态机配置
const states = {
  draft: {
    next: 'validation',
    actions: { /*...*/ }
  },
  validation: {
    next: 'payment',
    actions: { /*...*/ }
  }
  // 其他状态配置...
}

class StateMachine {
  constructor(initial) {
    this.current = initial
  }

  next() {
    const stateConfig = states[this.current]
    this.current = stateConfig.next
    stateConfig.actions.onTransition?.()
  }
}

命令模式的琐碎封装

为简单操作创建命令类会导致代码冗余:

// 反模式:简单操作的过度封装
class IncrementCommand {
  constructor(counter) {
    this.counter = counter
  }

  execute() {
    this.counter.value++
  }

  undo() {
    this.counter.value--
  }
}

// 使用
const cmd = new IncrementCommand(counter)
cmd.execute()

对于简单操作,使用函数更合适:

// 改进实现:函数式命令
function createCommand(execute, undo) {
  return { execute, undo }
}

const incrementCmd = createCommand(
  () => counter.value++,
  () => counter.value--
)

访问者模式的侵入性修改

访问者模式要求元素类预留访问接口,破坏了封装性:

// 反模式:元素类被迫接受访问者
class Element {
  accept(visitor) {
    visitor.visit(this)
  }
}

class Visitor {
  visit(element) {
    // 基于类型判断的处理逻辑
    if (element instanceof A) {
      /*...*/
    } else if (element instanceof B) {
      /*...*/
    }
  }
}

考虑使用模式匹配替代:

// 改进实现:外部处理器
function processElement(element) {
  switch (element.constructor) {
    case A:
      return handleA(element)
    case B:
      return handleB(element)
    default:
      return defaultHandler(element)
  }
}

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步