装饰器模式
装饰器模式(AOP
)是一种常见的设计模式,React
框架中的高阶函数就是使用了装饰器模式,比如一个高阶函数接受了一个基础组件,这个基础组件设计之初只是一个单纯的展示组件,并没有任何的数据处理。现在我们想在这个组件渲染后发送ajax
请求。
有两种实现方式
- 在原来组件的
componentDidMount
函数中发送ajax请求,这样会破坏了原来的展示组件。 - 编写一个高阶函数,返回一个新的组件,新的组件的
render
函数中返回展示组件,在新组件的componentDidMount
阶段发送ajax
请求。
显然,使用高阶函数(装饰器模式)能够很好的对已有功能进行拓展,这样不会更改原有的代码,对其他的业务产生影响,这方便我们在较少的改动下对软件功能进行拓展。
装饰器模式的两个经典函数
Function.prototype.before
Function.prototype.before = function(beforeFn) {
let _this = this
return function() {
beforeFn.apply(this, arguments)
return _this.apply(this, arguments) //返回原有函数执行结果
}
}
let f1 = function() {
alert(3)
}
f1 = f1.before(function() {
alert(2)
}).before(function() {
alert(1)
})
f1() // 页面上依次弹出1,2,3
Function.prototype.after
Function.prototype.after = function (afterFn) {
let _this = this
return function () {
var ret = _this.apply(this, arguments)
afterFn.apply(this, arguments)
return ret //返回原有函数执行结果
}
}
let f2 = function () {
console.log(1)
}
f2 = f2.after(function () {
console.log(2)
}).after(function () {
console.log(3)
})
f2() //控制台依次打印1,2,3
在
Function
的原型上拓展before
和after
方法尽管不推荐,但是这能很好的帮助我们了解到使用装饰器模式给我们业务或者框架开发带来的便捷,也就是能在不改动原有函数的基础上拓展功能
装饰器模式的使用示例
表单提交和校验分离
提交表单时需要进行非空校验,校验成功后发送ajax
请求,通常我们会这样做
// 伪代码
function onSubmit() {
var ret = checkISEmpty() //获取表单校验结果
if(ret) {
prompt('不能为空')
} else {
ajax() // 发送ajax请求
}
}
在
onSubmit
函数中进行了字段的非空校验和信息提交,这违背了函数功能的单一性原则。
下面我们试着用装饰器模式进行改造。
Function.prototype.before = function (beforeFn) {
if (typeof beforeFn !== 'function') {
throw new Error('beforeFn must be a function')
}
let _this = this
return function () {
if (!beforeFn.apply(this, arguments)) return
_this.apply(this, arguments)
}
}
function checkForm() {
let username = document.querySelector('form').username
if (!username.value || !username.value.trim()) {
alert('请输入用户名')
return false
}
return true
}
function submit() {
alert('用户信息提交成功')
}
submit = submit.before(checkForm)
document.querySelector('form').onsubmit = submit
函数参数修改
某个项目封装的ajax
请求中role
的值为administrator
,某天新增了一个接口请求要求role
的角色为超级管理员supreRole
。实现这个需求我们不考虑修改封装好的方法Request
,因为已有接口的角色都是administrator
.直接修改Request
方法需要改动多处,所以我们不妨考虑使用装饰器模式。
function Request(url, params) {
let baseParams = {role: 'administrator'}
requestParams = Object.assign({}, baseParams, params)
console.log(`正在发送ajax请求,请求参数为${JSON.stringify(requestParams)}`)
}
//正在发送ajax请求,请求参数为{"role":"administrator"}
Request('xxxxxxxx')
Request = Request.before(() => null)
// 正在发送ajax请求,请求参数为{"role":"supreRole"}
Request('xxxxxxxx', {role: 'supreRole'})
小结
使用装饰器模式能够使函数的功能尽可能保持单一,能够便于对已有功能进行拓展。装饰器模式可以形成一条很长的装饰链,比如上文alert
的1
, 2
,3
。副作用在于延长了函数的作用域链,在装饰链很长的情况下错误可能不好定位。