您现在的位置是:网站首页 > 常见设计模式误用与反模式文章详情
常见设计模式误用与反模式
陈川
【
JavaScript
】
9205人已围观
7921字
设计模式在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)
}
}
上一篇: 设计模式与代码可维护性的关系
下一篇: 如何选择合适的设计模式