自学内容网 自学内容网

装饰者模式

前言

在传统的面向对象语言中,给对象添加功能通常使用继承方式。这种方式并不灵活且存在一些问题:

随着业务复杂度增加,会创建出大量的子类,且会导致父子类之间存在强耦合性。

装饰者(decorator)模式,能够在不改变原始对象的基础上,在运行时动态地添加、删除或修改对象的功能。与继承相比,装饰者是一种更轻便灵活的做法。

当然前端也可实现装饰类,但前端中类本质上是构造函数的一种语法糖,此处不做讨论。

举个栗子

在日常项目中,我们会遇到这样的问题:有一个新需求,要在已有功能基础上增加一些内容。但老代码可能不是我们编写的,此时我们会面临如下问题

  • 不知道老代码的使用范围,随意改动容易影响之前的功能
  • 看懂老代码的逻辑再进行修改,比较耗时

我们来看一个例子:假设项目中有一个函数,在多个地方被调用,现在需要在调用这个函数前加一些逻辑。

// 原始函数
function oriFn(){
    console.log('执行原始函数');
}
// 找到原函数进行修改
function oriFn(){
    console.log('执行前置逻辑');
    console.log('执行原始函数');
}

当然在原函数足够简单时可以这么处理,如果原函数非常复杂,就需要考虑在不改变原函数的情况下,新增所需功能,一般做如下处理

let copyFn = oriFn;
oriFn = function(){
    console.log('执行前置逻辑');
    copyFn();
}

这种方式符合封闭开放原则,且未改动原函数。但其也存在一些问题

  • 必须新增一个临时变量,如果多次包装会产生多个非必要的变量
  • this指向问题,部分场景下需要手动处理(如document等)

AOP装饰函数

AOP(面向切面编程)。主要作用是把一些跟核心业务逻辑无关的功能抽离出来,再通过“动态织入”的方式掺入业务逻辑模块中,保证了业务逻辑模块的纯净和高内聚性。

// 前置装饰
Function.prototype.before = function(beforeFn){
    let _this = this;
    return function(){
        beforeFn.apply(this, arguments);
        return _this.apply(this, arguments);
    }
}
// 后置装饰
Function.prototype.after = function(afterFn){
    let _this = this;
    return function(){
        var ret = _this.apply(this, arguments);
        afterFn.apply(this, arguments);
        return ret;
    }
}

所以上面的例子可以改写为

// 定义前置函数
function beforeFn(){
    console.log('执行前置逻辑')
}
// 定义后置函数
function afterFn(){
    console.log('执行后置逻辑')
}

oriFn = oriFn.before(beforeFn).after(afterFn);
oriFn();
// 输出:执行前置逻辑
// 输出:执行原始函数
// 输出:执行后置逻辑

使用场景

通常用于日记统计、安全控制、异常处理等与业务逻辑无关的场景下。

便于理解,这里举个常见的例子:在表单提交前要做必填和数据校验,通常写法如下

// 校验
function validate() {
    if(xxx){
        console.log('校验失败')
        return false
    }
    return true
}
// 点击提交
function submit() {
    // 校验
    if(!validate()){
        return
    }
    // 发送请求
    console.log('发送请求')
    fecth('http://xxx.com/xxx')
}

如果此时有如下需求

  • 需要动态决定需不需要校验

  • 需要统计按钮点击次数

  • 需要上报用户各个行为做前端监控

按照一般写法就需要找到各个业务方法入口,不停地添加判断条件,这样做就会导致各个功能模块耦合严重,所以我们可以这么处理

Function.prototype.before = function (beforefn) {
    let self = this
    return function () {
        if (beforefn.name=='validate') {
        // 前置校验函数结果为false,不执行后续
            if(beforefn.apply(this, arguments) === false){
                return
            }
        }else{
            beforefn.apply(this, arguments)
        }
        return self.apply(this, arguments)
    }
}
// 统计函数
function getClickNum(){
    let _this = this;
    _this.num = 0;
    _this.addNum = function(){
        _this.num++;
    }
}
let clickFn = new getClickNum()
// 日志输出
function log() {
  console.log('用户点击了提交按钮')
  console.log('点击按钮次数:',clickFn.num)
}

// 直接调用
submit()
// 需要校验
submit = submit.before(validate)
// 需要统计
submit = submit.before(clickFn.addNum)
// 需要统计+日志
submit = submit.before(clickFn.addNum).after(log)
// 需要校验+统计+日志
submit = submit.before(validate).before(clickFn.addNum).after(log)

这样就可以做到各功能解耦,插拔式处理业务逻辑。

优缺点

优点

  • 符合开闭原则:在不修改已有代码逻辑的情况下扩展新的功能
  • 单一职责原则:将不同的功能实现封装到不同的装饰器中,每个装饰器功能确定,便于维护,且整体代码结构清晰
  • 动态组合:可根据不同的需求场景,通过组装不同装饰器实现功能

缺点

  • 增加复杂度:装饰者模式引入了额外的类和对象,某些情况下会占用较多的内存,且在使用时需要考虑装饰器之间的关系和顺序。
  • 增大运行时开销:每个装饰器都会增加一次方法调用的开销,会产生一定的性能损失。

原文地址:https://blog.csdn.net/a736755244/article/details/143016041

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!