一、引入
场景1: 你正在开开心心的写代码,代码由很多函数组成,其中的每个函数都可能会报错,此时你的处理方式是为每一个函数添加 try ... catch
:
// 除法函数
function div(a, b) {
try {
return a / c
} catch (e) {
console.log(e)
}
}
// 加法函数
function add(a, b) {
try {
return a + d
} catch (e) {
console.log(e)
}
}
无奈要进行错误捕获的函数太多,慢慢的代码变得混乱,可读性变得极差,每次维护时都让你叫苦不迭。
场景2: 你正在开发一个内容网站,用户点击内容(文章、视频、评论等)都需要检查用户是否买了会员:
- 获取内容前检查用户是否买了会员。
- 如果用户购买会员,则请求对应的内容供用户阅读。
你的处理是:
// 用户点击文章
function getArticle() {
const check = new Promise((resolve, reject) => {
// ... 检查用户是否是会员
})
check.then(res => {
if (用户是会员) {
// 展示内容
} else {
// 提醒用户开会员
}
})
}
// 用户点击视频
function getVideo() {
const check = new Promise((resolve, reject) => {
// ... 检查用户是否是会员
})
check.then(res => {
if (用户是会员) {
// 展示内容
} else {
// 提醒用户开会员
}
})
}
...
上述场景的共同痛点在于:非核心功能(如异常处理、权限校验)与核心业务逻辑紧密交织,导致代码难以复用、扩展和维护。此时,装饰器模式(Decorator Pattern) 应运而生 —— 它通过 “包裹” 目标函数的方式,将横切逻辑从核心代码中分离,实现非侵入式的功能增强。
读完本文,你将学会如何利用装饰器模式优雅地解决这类问题,让代码在保持简洁的同时,具备更强的可维护性和扩展性。
在阅读本文之前,请确认你已经了解了闭包的概念。
二、何为装饰器
装饰器模式(Decorator Pattern)是一种结构型设计模式,其核心思想是:在不修改原有对象代码的前提下,动态地为对象添加新功能。它通过创建一个包装对象(即 “装饰器”)来包裹原始对象,从而在保持接口一致性的同时,增强对象的行为。
原始对象 → 装饰器A(原始对象) → 装饰器B(装饰器A(原始对象)) → 装饰器C(装饰器B(...))
每个装饰器都可以在不影响其他装饰器的前提下,独立地增强对象功能。
现在让我们用装饰器实现这样的功能:每个函数调用之前执行一些事情:
/*
* 在函数的原型上定义一个 before 函数
* before 函数接受一个函数作为参数,也就是先于目标函数执行的函数
* 该函数返回一个新的函数
* 新函数在调用原函数之前会首先调用 beforeFn
* 然后再调用原来的函数
*/
Function.prototype.before = function (beforeFn) {
const that = this
return function (...args) {
beforeFn.apply(this, args)
return that.apply(this, args)
}
}
// 将要被装饰的函数,我们希望调用它之间执行一些事情
const test = function () {
console.log('test函数被执行')
}
// 获取装饰后的函数
const test1 = text.before(() => {
console.log('前置函数被执行')
})
test1() // 前置函数被执行
// test函数被执行
这就是一个简单的装饰器,我们通过将原函数包裹一层,然后在两层之间可以做我们自己的一些事情,又不会影响原函数的执行。
当然,我们也可以定义类似的后置函数,在目标函数后执行:
Function.prototype.before = function (beforeFn) {
const that = this
return function (...args) {
beforeFn.apply(this, args)
return that.apply(this, args)
}
}
Function.prototype.after = function (afterFn) {
const that = this
return function (...args) {
const res = that.apply(this, args)
afterFn.apply(this, args)
return res
}
}
const test = function () {
console.log('函数被执行')
console.log(this)
}
const test1 = test.before(() => {
console.log('前置函数被执行')
}).after(() => {
console.log('后置函数被执行')
})
test1() // 前置函数被执行
// test函数被执行
// 后置函数被执行
三、代码改造
在了解装饰器后,我们可以尝试对最开始提出的两个场景的代码进行改造:
1. 错误集中捕获
function catchError(fn) {
return function (...args) {
// 集中在此处进行错误捕获
try {
const res = fn.apply(this, args)
return res
} catch (e) {
console.log('错误捕获', e)
}
}
}
function div(a, b) {
// 原函数内不需要错误捕获
return a / c
}
function add(a, b) {
return a + d
}
// 将原函数进行装饰
const catchDiv = catchError(div)
const catchAdd = catchError(add)
console.log(catchDiv(10, 0)) // c not defined
console.log(catchAdd(1, 1)) // d not defined
2. 权限检查
function auth(fn) {
return function(...args) {
new Promise((resolve, reject) => {
// 异步获取权限
checkPermission().then(res => {
if (res.isAuth) {
const result = fn.apply(this, args);
resolve(result)
} else {
reject(new Error("无权限"))
}
}).catch(err => {
reject(err); // 处理权限请求失败
})
})
}
}
const decoratedGetArticle = auth(getArticle)
const decoratedGetVideo = auth(getVideo)
decoratedGetArticle().then(res => { ... 其它代码 })
decoratedGetVideo().then(res => { ... 其它代码})
四、装饰器的优点
维度 | 装饰器模式 | 继承 |
---|---|---|
扩展性 | 动态扩展,运行时决定增强逻辑 | 静态扩展,编译时确定 |
灵活性 | 可组合多个装饰器,功能自由叠加 | 受限于单继承,功能组合不灵活 |
代码侵入性 | 非侵入式,不修改原始对象代码 | 需要创建子类,可能修改父类 |
应用场景 | 功能可插拔、需要灵活组合的场景 | 固定的、层级化的功能扩展 |