JS设计模式(3):装饰器模式

一、引入

场景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: 你正在开发一个内容网站,用户点击内容(文章、视频、评论等)都需要检查用户是否买了会员:

  1. 获取内容前检查用户是否买了会员。
  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 => { ... 其它代码})

四、装饰器的优点

维度装饰器模式继承
扩展性动态扩展,运行时决定增强逻辑静态扩展,编译时确定
灵活性可组合多个装饰器,功能自由叠加受限于单继承,功能组合不灵活
代码侵入性非侵入式,不修改原始对象代码需要创建子类,可能修改父类
应用场景功能可插拔、需要灵活组合的场景固定的、层级化的功能扩展
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值