您现在的位置是:网站首页 > 拒绝设计模式(“设计模式都是花架子”)文章详情
拒绝设计模式(“设计模式都是花架子”)
陈川
【
前端综合
】
54990人已围观
6044字
设计模式在前端开发中经常被奉为圭臬,但过度依赖它们可能导致代码复杂化。有些场景下,简单直接的实现反而更高效,盲目套用模式可能适得其反。
设计模式的本质与滥用
设计模式本质是解决特定场景问题的经验总结,而非银弹。前端领域常见的滥用现象包括:
- 过度工程化:为简单的数据展示强行引入Redux
- 模式嵌套:在200行代码里同时使用工厂模式+策略模式+装饰器
- 形式主义:为了"符合模式"而扭曲业务逻辑
// 典型滥用示例:简单表单验证硬套策略模式
const strategies = {
isNonEmpty: (value) => value !== '',
minLength: (value, length) => value.length >= length,
isMobile: (value) => /^1[3-9]\d{9}$/.test(value)
}
class Validator {
constructor() {
this.cache = []
}
add(dom, rule, errMsg) {
// ...繁琐的策略注册逻辑
}
start() {
// ...复杂的验证执行流程
}
}
// 实际用普通函数更直观
function validateForm(data) {
if (!data.name) return '姓名不能为空'
if (data.phone.length !== 11) return '手机号格式错误'
// ...其他简单校验
}
前端特有的轻量级实践
现代前端框架已经内置了许多模式的最佳实践,额外引入经典设计模式可能多余:
- React Hooks 替代了大部分观察者模式场景
- Vue Composition API 比传统工厂模式更灵活
- Svelte响应式系统 自动处理状态变更通知
// 使用React Context比传统中介者模式更简洁
const UserContext = createContext()
function App() {
const [user, setUser] = useState(null)
return (
<UserContext.Provider value={{ user, setUser }}>
<Navbar />
<Profile />
</UserContext.Provider>
)
}
function Profile() {
const { user } = useContext(UserContext)
return <div>{user?.name}</div>
}
模式替代方案的具体案例
状态管理场景
Redux典型实现需要定义action、reducer、store等样板代码。现代方案如Zustand大幅简化:
// 传统Redux
const store = createStore(reducer, applyMiddleware(thunk))
// Zustand实现同等功能
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 }))
}))
DOM操作场景
jQuery时代常用的命令式操作,现在完全可以用声明式替代:
// 传统命令式DOM操作
$('.btn').on('click', function() {
$(this).toggleClass('active')
$('#panel').slideToggle()
})
// 现代声明式方案
function Button() {
const [active, setActive] = useState(false)
return (
<>
<button
className={active ? 'active' : ''}
onClick={() => setActive(!active)}
>
Toggle
</button>
<div style={{ display: active ? 'block' : 'none' }}>
Content
</div>
</>
)
}
何时真正需要设计模式
并非全盘否定设计模式,而是强调按需使用:
- 复杂状态机:游戏开发中可能需要状态模式
- 可插拔架构:需要支持动态扩展的插件系统
- 高频重构场景:早期采用桥接模式为未来留余地
// 适合使用装饰器模式的场景:API请求日志
function logRequest(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value
descriptor.value = async function(...args: any[]) {
console.log(`Calling ${key} with`, args)
const result = await original.apply(this, args)
console.log(`Called ${key}, result`, result)
return result
}
return descriptor
}
class ApiClient {
@logRequest
async getUser(id: string) {
return fetch(`/users/${id}`)
}
}
前端开发的实用主义原则
-
YAGNI原则 (You Aren't Gonna Need It)
- 不要为假设的未来需求提前引入模式
- 每次提交的代码应该解决当前具体问题
-
KISS原则 (Keep It Simple, Stupid)
- 表单验证直接用if-else可能比策略模式更合适
- 组件通信优先考虑props drilling而非立即上状态管理
-
可读性优先
- 新同事能否在5分钟内理解这段代码?
- 模式使用是否让代码更清晰而非更晦涩?
// 符合实用主义的代码示例
function calculatePrice(items) {
let total = 0
// 清晰可见的折扣规则
if (items.length > 10) {
total = items.reduce((sum, item) => sum + item.price * 0.8, 0)
}
else if (items.some(item => item.isPremium)) {
total = items.reduce((sum, item) => sum + (item.isPremium ? item.price * 0.9 : item.price), 0)
}
else {
total = items.reduce((sum, item) => sum + item.price, 0)
}
return total > 1000 ? total * 0.95 : total
}
框架语境下的模式演进
现代框架自身已经实现了许多经典模式:
- React Fiber架构:本质是责任链模式+调度器模式
- Vue响应式系统:自动依赖追踪的观察者模式变体
- Svelte编译优化:相当于内置的享元模式实现
开发者应该优先掌握框架本身的机制,而非强行套用传统模式:
// React新文档推荐的简单状态管理
function useCounter() {
const [count, setCount] = useState(0)
const increment = () => setCount(c => c + 1)
return { count, increment }
}
function App() {
const counter1 = useCounter()
const counter2 = useCounter()
return (
<>
<button onClick={counter1.increment}>
Counter 1: {counter1.count}
</button>
<button onClick={counter2.increment}>
Counter 2: {counter2.count}
</button>
</>
)
}
代码坏味道的识别标准
当出现以下症状时,可能需要重新评估模式使用:
- 文件跳转过多:需要查看5个以上文件才能理解核心逻辑
- 模式嵌套过深:一个功能同时使用3种以上设计模式
- 修改成本剧增:简单需求变更需要修改多处模式相关代码
- 团队认知负担:每次代码评审都要解释模式实现细节
// 坏味道示例:过度抽象的工厂模式
class ButtonFactory {
static createButton(type) {
switch(type) {
case 'primary':
return new PrimaryButton()
case 'danger':
return new DangerButton()
default:
return new DefaultButton()
}
}
}
// 更直接的实现
function Button({ type, children }) {
const className = `btn ${type === 'primary' ? 'btn-primary' :
type === 'danger' ? 'btn-danger' : 'btn-default'}`
return <button className={className}>{children}</button>
}
前端特定领域的简化模式
某些传统模式在前端领域有更轻量的替代方案:
传统模式 | 前端简化方案 |
---|---|
观察者模式 | CustomEvent / EventEmitter |
装饰器模式 | HOC / Hook包装 |
适配器模式 | 中间件函数 |
单例模式 | 模块导出实例 |
// 事件总线替代观察者模式
const bus = new EventTarget()
// 发布事件
bus.dispatchEvent(new CustomEvent('login', { detail: user }))
// 订阅事件
bus.addEventListener('login', (e) => {
console.log('User logged in:', e.detail)
})
性能视角的模式取舍
某些模式会带来性能损耗,需要权衡:
- 虚拟代理模式:可能增加内存占用
- 装饰器链:函数调用栈变深影响执行速度
- 复杂观察者:频繁事件触发导致渲染卡顿
// 性能敏感的动画场景应避免过度抽象
function animateElement(el) {
// 直接操作比经过多层代理更高效
let start = null
function step(timestamp) {
if (!start) start = timestamp
const progress = timestamp - start
el.style.transform = `translateX(${Math.min(progress / 10, 200)}px)`
if (progress < 2000) {
window.requestAnimationFrame(step)
}
}
window.requestAnimationFrame(step)
}
团队协作的编码共识
在团队环境中更应注重实际效果:
- 模式采用标准:建立明确的模式使用checklist
- 代码审查重点:是否真的解决了问题而非是否"够模式化"
- 文档规范:记录已被验证有效的模式应用场景
<!-- 团队文档示例 -->
## 状态管理采用标准
✅ 采用条件:
- 超过3个层级组件需要共享状态
- 存在复杂的跨组件状态更新逻辑
- 需要持久化/回放状态历史
❌ 避免场景:
- 仅父子组件通信
- 简单表单状态
- 一次性页面状态