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('发射原子弹');
}
}
装饰者对象和它所装饰的对象拥有一致的接口 -> 透明的 -> 可以递归地嵌套任意多装饰者对象
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 装饰者模式
- 目的:为对象动态添加行为