关于装饰器模式我所知道的

133 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

关键词:结构型模式 Decorator Wrapper 动态增加功能 原型高达挂载武器模块

在设计上更像是 AOP 的设计思路。也可以理解成 react 中的 HOC。

解决什么问题?

装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。

它主要的作用是给原始类添加增强功能。用于一开始不能确定对象的全部功能时,为对象动态加入行为,也可以说是增强功能。

相对于继承,有两个特殊的地方:

  1. 装饰器和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。
  2. 装饰器是对功能的增强,这也是装饰器模式应用场景的一个重要特点。

举个例子:

继承的加强类似假两件,装饰器的加强类似单品穿搭。

class Body {
  wear() {
    console.log('Body!')
  }
}

class TshirtDecorator {
  constructor(body) {
    this.body = body
  }
  wear() {
    this.body.wear();
    console.log('穿T恤')
  }
}

class JacketDecorator {
  constructor(body) {
    this.body = body
  }
  wear() {
    this.body.wear()
    console.log('穿夹克')
  }
}

let p = new Body();
p = new TshirtDecorator(p)
p = new JacketDecorator(p)

p.wear()
// Body!
// 穿T恤
// 穿夹克

应用场景

不希望某个类天生就非常庞大,一次性包含许多职责。那么我们就可以使用装饰者模式。

侧重于功能,或者拆分方法的时候,推荐使用装饰器模式。并且可以嵌套多个装饰器类。

具体实践

原始代码

修改为一个简单的 HOC 组件

外面再嵌套一个装饰器

工具函数一览


const before = (fn, beforefn) => {
  return function () {
    beforefn.apply(this, arguments)
    return fn.apply(this,arguments)
  }
}

const after = (fn, afterfn) => {
  return function () {
    const res = fn.apply(this,arguments)
    afterfn.apply(this, arguments)
    return res
  }
}

let bf = before(function () { alert(3) }, function () { alert(2) })

bf = before(bf, function () { alert(1) })

bf()

let af = after(function () { alert(3) }, function () { alert(2) })

af = after(af, function () { alert(1) })

af()

数据统计上报

// html
<button id="btn" tag="login">点击打开登录框</button>

const log = (tag) => {
  console.log(`上报标签为${tag}`)
}

function showLogin() {
  console.log('打开弹窗', this)
  log(this.getAttribute('tag'))
}

// 页面登录按钮,点击弹出登录框,并进行数据上报
document.getElementById('btn').addEventListener('click', showLogin)

解耦数据上报和登录弹窗两项功能。

const after = (fn, afterfn) => {
  return function () {
    const res = fn.apply(this, arguments)
    afterfn.apply(this, arguments)
    return res
  }
}

function showLogin() {
  console.log('打开弹窗')
}

function log() {
  console.log(`上报标签为${this.getAttribute('tag')}`)
}

showLogin = after(showLogin, log)

document.getElementById('btn').addEventListener('click', showLogin)

动态修改函数参数

const before = (fn, beforefn) => {
  return function () {
    beforefn.apply(this, arguments)
    return fn.apply(this, arguments)
  }
}

const getToken = () => 'token'

function ajax(type, url, params) {
  console.table(params)
}

// 给请求添加token
ajax = before(ajax, function (type, url, params) {
  params.token = getToken()
})


ajax('get', 'url', { name: 'kane' })

插件式表单验证

// html
// 用户名 <input type="text" id="name"/>
// 密码 <input type="password" id="pwd"/>
// <button id="btn" tag="login">提交</button>

const name = document.getElementById('name');
const pwd = document.getElementById('pwd');
const btn = document.getElementById('btn');

Function.prototype.before = function (beforefn) {
  const _this = this;
  return function () {
    if (beforefn.apply(this, arguments) === false) return;
    return _this.apply(this, arguments)
  }
}

const validata = () => {
  if (name.value === '' || pwd.value === '') {
    alert(`不能为空`)
    return false
  }
  return true
}

let submit = () => {
  const params = {
    name: name.value,
    pwd: pwd.value,
  }
  // 提交数据
  console.log(params)
}

submit = submit.before(validata)

btn.onclick = () => {
  submit()
}

参考资料

hoc pattern

refactoringguru decorator