装饰器模式

装饰器模式(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的原型上拓展beforeafter方法尽管不推荐,但是这能很好的帮助我们了解到使用装饰器模式给我们业务或者框架开发带来的便捷,也就是能在不改动原有函数的基础上拓展功能

装饰器模式的使用示例

表单提交和校验分离

提交表单时需要进行非空校验,校验成功后发送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'})

小结

使用装饰器模式能够使函数的功能尽可能保持单一,能够便于对已有功能进行拓展。装饰器模式可以形成一条很长的装饰链,比如上文alert1, 23。副作用在于延长了函数的作用域链,在装饰链很长的情况下错误可能不好定位。