12_装饰者模式

1 简介

为对象动态增加职责:不改变对象自身的基础上,在程序运行期间给对象动态地添加职责

2 模拟传统面向对象语言的装饰者模式

编写一个飞机大战的游戏,随着经验值的增加,操作的飞行对象可以升级成为更厉害的飞机

2.1 原始飞机类

class Plane {
  constructor() {}
  fire() {
    console.log('发射普通子弹');
  }
}

2.2 +导弹和原子弹

class MissileDecorator {
  constructor(plane) {
    this.plane = plane
  }
  fire() {
    this.plane.fire()
    console.log('发射导弹');
  }
}
class AtomDecorator {
  constructor(plane) {
    this.plane = plane
  }
  fire() {
    this.plane.fire()
    console.log('发射原子弹');
  }
}

装饰者对象和它所装饰的对象拥有一致的接口 -> 透明的 -> 可以递归地嵌套任意多装饰者对象

image.png

3 装饰者也是包装器

  • 装饰者模式将一个对象嵌入另一个对象之中,相当于这个对象被另一个对象包装起来,形成一条包装链

4 js的装饰者

let plane = {
  fire() {
    console.log('发射普通子弹');
  }
}
let missileDecorator = function() {
  console.log('发射导弹');
}
let atomDecorator = function() {
  console.log('发射原子弹');
}
let fire1 = plane.fire
plane.fire = function() {
  fire1()
  missileDecorator()
}
let fire2 = plane.fire
plane.fire = function() {
  fire2()
  atomDecorator()
}
plane.fire()

5 装饰函数

  • 符合开放封闭原则

存在的问题

  • 需要维护中间变量
  • this被劫持
let _getElementById = document.getElementById;
document.getElementById = function(id) {
  alert(1)
  return _getElementById(id)
  // caught TypeError: 
  // Illegal invocation at document.getElementById (
}
let button = document.getElementById('button')

函数内部 this 预期指向 document 而不是 window

let _getElementById = document.getElementById;
document.getElementById = function(id) {
  alert(1)
  return _getElementById.apply(document, id)
}
let button = document.getElementById('button')

6 用AOP装饰函数

Function.prototype.before 和 Function.prototype.after

Function.prototype.before = function(beforefn) {
  // 保存原函数的引用
  let __self = this;
  // 返回包含了原函数和新函数的"代理"函数
  return function() {
    // 执行新函数,保证 this不被劫持
    beforefn.apply(this, arguments);
    // 执行原函数,保证 this不被劫持
    return __self.apply(this, arguments);
  }
};
Function.prototype.after = function(afterfn) {
  let __self = this;
  return function() {
    let ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  }
};

7 AOP应用

7.1 数据上报

let showLogin = function() {
  console.log('打开登录浮层');
  log(this.getAttribute('tag'))
}
let log = function(tag) {
  console.log('上报标签为:' + tag);
}
document.getElementById('button').onclick = showLogin

showLogin 函数负责打开登录浮层+数据上报 -> 功能耦合

  • 使用AOP分离
Function.prototype.after = function(afterfn) {
  let __self = this;
  return function() {
    let ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  }
};
let showLogin = function() {
  console.log('打开登录浮层');
}
let log = function() {
  console.log('上报标签为:' + this.getAttribute('tag'));
}
showLogin = showLogin.after(log)
document.getElementById('button').onclick = showLogin

7.2 用AOP动态改变函数的参数

Function.prototype.before = function(beforefn) {
  let __self = this;
  return function() {
    beforefn.apply(this, arguments);
    return __self.apply(this, arguments);
  }
};
let ajax = function(type, url, param) {
  console.log(param);
}
let getToken = function() {
  return 'Token'
}
ajax = ajax.before(function(type, url, param) {
  param.Token = getToken()
})
ajax('get', 'http://xxx', { name: 'july' })

7.3 插件式的表单验证

let formSubmit = function() {
  if(username.value === '') {
    return alert('用户名不能为空')
  }
  if(password.value === '') {
    return alert('密码不能为空')
  }
  let param = {
    username: username.value,
    password: password.value
  }
  console.log(param);
  // ajax(...)
}
submitBtn.onclick = function() {
  formSubmit()
}

formSubmit函数负责提交ajax请求+验证输入 -> 函数臃肿,职责混乱,无复用性

  • 分离校验输入和提交ajax请求的代码
let validate = function() {
  if(username.value === '') {
    alert('用户名不能为空')
    return false
  }
  if(password.value === '') {
    alert('密码不能为空')
    return false
  }
}
let formSubmit = function() {
  if(validate() === false) {
    return
  }
  let param = {
    username: username.value,
    password: password.value
  }
  console.log(param);
}
submitBtn.onclick = function() {
  formSubmit()
}

formSubmit函数内部需要计算validate函数返回值 -> 耦合

  • 完全分离函数
Function.prototype.before = function(beforefn) {
  let __self = this;
  return function() {
    if(beforefn.apply(this, arguments) === false) {
      return 
    }
    return __self.apply(this, arguments);
  }
};
let validate = function() {
  if(username.value === '') {
    alert('用户名不能为空')
    return false
  }
  if(password.value === '') {
    alert('密码不能为空')
    return false
  }
}
let formSubmit = function() {
  let param = {
    username: username.value,
    password: password.value
  }
  console.log(param);
}
formSubmit = formSubmit.before(validate)
submitBtn.onclick = function() {
  formSubmit()
}

8 装饰者模式和代理模式

主要区别:意图和设计目的

8.1 代理模式

  • 目的:当直接访问本体不方便或不符合需求时,为本体提供一个替代者

本体定义了关键功能,代理提供或拒绝对它的访问,或者访问前做额外处理

8.2 装饰者模式

  • 目的:为对象动态添加行为
posted on 2023-05-09 10:52  pleaseAnswer  阅读(9)  评论(0编辑  收藏  举报