编码技巧收集二 ~ 责任链模式中间件

243 阅读5分钟

责任链模式

什么是责任链模式

责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。(此处引自 gof 设计模式)

在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。

责任链模式是一种对象行为型模式,其主要优点如下:

  1. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  2. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  3. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  4. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  5. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

其主要缺点如下。

  1. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

其他说明

责任链模式,总的一个核心就是请求者不必知道是谁哪个节点对象处理的请求,由于处理请求的可以在不同对象下处理,所以请求者跟接受者是解耦的。

纯的责任链:要求请求在这些对象链中必须被处理,而且一个节点处理对象,要么只处理请求,要么把请求转发给下个节点对象处理;

不纯的责任链:要求在责任链里不一定会有处理结构,而且一个节点对象,即可以处理部分请求,并把请求再转发下个节点处理;

适用场景

       职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

  1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
  2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  3. 可动态指定一组对象处理请求。

代码实现

class Middleware {
    constructor() {
        this.$cache = []
        this.$middlewares = []
    }

    // 注册中间件
    use() {
    [...arguments].forEach(item => {
            if (typeof item === 'function') {
                this.$cache.push(item)
            }
        })
        return this
    }

    /**
     * 每个中间件只有两个形参 第一是传进来的参数 第二个是调用下一个中间件的函数
     * 中间件的执行顺序是根据你注册中间件的顺序来去调用的 
     */
    next(params) {
        while (this.$middlewares.length) {
            const ware = this.$middlewares.shift()
            ware.call(this, params, this.next.bind(this))
        }
    }

    execute(params) {
        this.$middlewares = this.$cache.map(fn => { // 复制一份
            return fn;
        });
        this.next(params)
    }

}

export default Middleware

中间件升级-事件回调

每个中间件的过程都是不可控制的,全部都交由中间类去统一调用,我们可以加入事件回调,方便我们在中间件处理过程中拥有额外的逻辑能力

/**
 * 注册事件
 * @param {String} name 事件名称 
 * @param {Function (params)} callback 回调函数 
 */
on(name, callback) {
    if (typeof callback === 'function') {
        this.$events[name] = callback
    } else {
        throw '事件回调必须为函数'
    }
}

/**
 * 发射(触发)事件
 * @param {String} name 事件名称 
 * @param {Any} params 回调参数 
 */
emit(name, params) {
    if (this.$events[name]) {
        let callback = this.$events[name]
        callback.call(this, params)
    } else {
        throw '没有注册这个事件'
    }
}

将上述的使用方法再改造一下,方便实际业务中使用

function send(options, next) {
    this.emit('request', options)
    setTimeout(() => { // 模拟异步
        console.log('send', options.data);
        this.emit('response', options)
        options.promise.resolve({
            data: options.data
        })
    }, 100);
}

// 请求之前的回调函数
middleware.on('request', params => {
    // 在这里可以做请求之前的一些处理,比如添加全局参数等
    console.log(params, '再多做一些处理')
})

// 请求成功的回调函数
middleware.on('response', params => {
    // 在这里可以做下请求成功的一些处理,比如全局loading什么的
    console.log(params, '请求成功')
})

middleware.use(transform).use(validate).use(send)

middleware.executeFc({
    data: {
        name: 'cookie',
        age: '20'
    }
}).then(({
    data
}) => {
    console.log('finally', data)
});

多种条件判断

将流程化执行的多种条件判断通过中间件解耦,可以使得条件判断方法更加清晰。一般当你需要使用中介者来改造业务逻辑的时候,前端的项目确实有点复杂了。

const middleware = new Middleware()

function judge1(options, next) { // 空数校验
    if (!options.data) {
        options.promise.reject({
            data: false,
            msg: '数据为空'
        })
        return
    }
    next(options); // 通过验证
}

function judge2(options, next) { // 判断小于10
    if (options.data < 10) {
        options.promise.reject({
            data: false,
            msg: '数据小于10'
        })
        return
    }
    next(options); // 通过验证
}

function judge3(options, next) { // 判断大于30
    if (options.data < 30) {
        options.promise.reject({
            data: false,
            msg: '数据小于30'
        })
        return
    }
    options.promise.resolve({
        data: true,
        msg: '数据小于30'
    })
}

middleware.use(judge1).use(judge2).use(judge3)

middleware.executeFc({
    data: 40
}).then(({
    data
}) => {
    console.log('finally', data)
}).catch(({
    msg
}) => {
    console.log(msg)
})

责任链模式的优缺点

优点

  • 将请求与处理解耦

  • 请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转发给下一个节点。

  • 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果

  • 易于拓展新的请求处理类,符合开闭原则

  • 链路结构灵活,可以通过改变链路结构动态的新增或者删除责任。

缺点

  • 责任链太长或者处理时间太长,会影响整体性能

  • 如果节点对象存在循环引用会造成死循环,导致系统崩溃。