前言

职责链模式,它为请求的发送者和请求者之间实现了解耦,他将具体的请求者连成一条链,并沿着这条链依次处理请求,直到有一个对象处理它为止。

这么说可能不好理解,其实就是将复杂的条件判断进行了解耦处理,具体我们看下面的例子。

商城购买

我们现在有一个优惠活动,如果用户交了500元定金,他在购买的时候可以收到100元优惠卷,如果是交了200元定金则收到50元优惠卷,如果没有定金,那就没有优惠卷,库存有限的情况下不一定保证能买到。

假设我们有这三个参数:

  • orderType:表示订单类型(定金用户或者普通购买用户),code 的值为 1 的时候是 500 元 定金用户,为 2 的时候是 200 元定金用户,为 3 的时候是普通购买用户。
  • pay:表示用户是否已经支付定金,值为 true 或者 false, 虽然用户已经下过 500 元定金的 订单,但如果他一直没有支付定金,现在只能降级进入普通购买模式。
  • stock:表示当前用于普通购买的手机库存数量,已经支付过 500 元或者 200 元定金的用 户不受此限制。

那么我们的代码如下:

var order = function(orderType, pay, stock) {
    if (orderType === 1) { // 500 元定金购买模式
        if (pay === true) { // 已支付定金
            console.log('500 元定金预购, 得到 100 优惠券');
        } else { // 未支付定金,降级到普通购买模式
            if (stock > 0) { // 用于普通购买的手机还有库存
                console.log('普通购买, 无优惠券');
            } else {
                console.log('手机库存不足');
            }
        }
    } else if (orderType === 2) { // 200 元定金购买模式
        if (pay === true) {
            console.log('200 元定金预购, 得到 50 优惠券');
        } else {
            if (stock > 0) {
                console.log('普通购买, 无优惠券');
            } else {
                console.log('手机库存不足');
            }
        }
    } else if (orderType === 3) {
        if (stock > 0) {
            console.log('普通购买, 无优惠券');
        } else {
            console.log('手机库存不足');
        }
    }
};

order(1, true, 500); // 输出: 500 元定金预购, 得到 100 优惠券

这个函数实现了我们想要的功能,但是它里面充斥着大量的条件判断语句,当我们还需要新增更多的优惠时,它的代码量将会不断增大,维护难度也直线上升。

使用职责链模式重构

我们将每个优惠封装成一个函数,函数接收这三个参数进行处理,如果这个优惠不是它处理的,它调用其他优惠处理函数。

// 500 元订单
var order500 = function(orderType, pay, stock) {
    if (orderType === 1 && pay === true) {
        console.log('500 元定金预购, 得到 100 优惠券');
    } else {
        order200(orderType, pay, stock); // 将请求传递给 200 元订单
    }
};

// 200 元订单
var order200 = function(orderType, pay, stock) {
    if (orderType === 2 && pay === true) {
        console.log('200 元定金预购, 得到 50 优惠券');
    } else {
        orderNormal(orderType, pay, stock); // 将请求传递给普通订单
    }
};

// 普通购买订单
var orderNormal = function(orderType, pay, stock) {
    if (stock > 0) {
        console.log('普通购买, 无优惠券');
    } else {
        console.log('手机库存不足');
    }
};

// 测试结果:
order500(1, true, 500); // 输出:500 元定金预购, 得到 100 优惠券
order500(1, false, 500); // 输出:普通购买, 无优惠券
order500(2, true, 500); // 输出:200 元定金预购, 得到 500 优惠券
order500(3, false, 500); // 输出:普通购买, 无优惠券
order500(3, false, 0); // 输出:手机库存不足

可以看到,现在我们的代码就清晰了很多,它通过上一个优惠函数调用下一个优惠函数,实现了链条,但是这种实现非常僵硬,它使得优惠函数之间的关系是强耦合状态,违反了开放-封闭原则,如果我们要调整优惠的顺序,或者新增优惠,还是需要改动函数。

灵活的可拆分职责链

处理这种问题的办法就是规范化,我们规定优惠函数在不是自己处理的情况下,返回一个约定的值,然后外部通过这个值来判断是否传递给下一个优惠函数。

var order500 = function(orderType, pay, stock) {
    if (orderType === 1 && pay === true) {
        console.log('500 元定金预购,得到 100 优惠券');
    } else {
        return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
    }
};

var order200 = function(orderType, pay, stock) {
    if (orderType === 2 && pay === true) {
        console.log('200 元定金预购,得到 50 优惠券');
    } else {
        return 'nextSuccessor'; // 我不知道下一个节点是谁,反正把请求往后面传递
    }
};

var orderNormal = function(orderType, pay, stock) {
    if (stock > 0) {
        console.log('普通购买,无优惠券');
    } else {
        console.log('手机库存不足');
    }
};

封装一个用于链接优惠函数的方法:

var Chain = function(fn) {
    this.fn = fn;
    this.successor = null;
};

Chain.prototype.setNextSuccessor = function(successor) {
    return this.successor = successor;
};

Chain.prototype.passRequest = function() {
    var ret = this.fn.apply(this, arguments);
    if (ret === 'nextSuccessor') {
        return this.successor && this.successor.passRequest.apply(this.successor, arguments);
    }
    return ret;
};

使用setNextSuccessor来链接优惠函数,通过统一调用passRequest来运行优惠函数。

var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);

chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);

chainOrder500.passRequest(1, true, 500); // 输出:500 元定金预购,得到 100 优惠券
chainOrder500.passRequest(2, true, 500); // 输出:200 元定金预购,得到 50 优惠券
chainOrder500.passRequest(3, true, 500); // 输出:普通购买,无优惠券
chainOrder500.passRequest(1, false, 0); // 输出:手机库存不足

现在我们如果要新增优惠函数,就不需要改动原来的优惠函数了。

异步职责链

如果存在异步的情况,后一个优惠函数的处理需要等待上一个函数结束才行,而处理异步有两种方式,一种是Promise,一种是回调函数,我们以回调函数为例。

Chain.prototype.next = function() {
    return this.successor && this.successor.passRequest.apply(this.successor, arguments);
};

var fn1 = new Chain(function() {
    console.log(1);
    return 'nextSuccessor';
});

var fn2 = new Chain(function() {
    console.log(2);
    var self = this;
    setTimeout(function() {
        self.next();
    }, 1000);
});

var fn3 = new Chain(function() {
    console.log(3);
});

fn1.setNextSuccessor(fn2).setNextSuccessor(fn3);
fn1.passRequest();

这种写法其实并不完善,大家参考思路即可,因为上面的写法导致return的结果没有参考意义了,我们还需要考虑到这点,使用promise和async await可能会更加合适。

职责链模式的优缺点

现在我们再回过头来看开头,职责链模式确实是将发送者和N个请求者之间实现了解耦,由于不知道到底是谁可以处理请求,所以需要从第一个请求者开始。

通过职责链模式,我们可以很方便的组合链条,甚至有时候可以单独拿出某一个链条节点来单独使用。

var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);

chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);

chainOrder200.passRequest(2, true, 500); // 输出:200 元定金预购,得到 50 优惠券

但是这种模式也不是没有弊端,如果所有的请求者都不处理,那就导致我们根本不知道这个请求处理了没有,解决的办法就是给末尾增加一个特殊处理,如果到它了,就抛出一个异常,这样来实现一个通知作用。

另外由于职责链模式是一个个运行的,如果链条中存在大量的请求者,从性能的考虑,大部分的请求者都没有实质性的作用,只是传递,为此我们还需要避免职责链过长带来的性能损耗。

AOP实现职责链

Function.prototype.after = function(fn) {
    var self = this;
    return function() {
        var ret = self.apply(this, arguments);
        if (ret === 'nextSuccessor') {
            return fn.apply(this, arguments);
        }
        return ret;
    }
};

var order = order500yuan.after(order200yuan).after(orderNormal);

order(1, true, 500); // 输出:500 元定金预购,得到 100 优惠券
order(2, true, 500); // 输出:200 元定金预购,得到 50 优惠券
order(1, false, 500); // 输出:普通购买,无优惠券

当我们运行order的时候,实际上运行的是最后一个after抛出的匿名函数,这个匿名函数会运行参数fn,等待fn的结果,而fn又是上一个after返回的匿名函数,它又会运行它的参数fn,以此类推,直到运行到最顶层的order500yuan函数,等他结果出来后,再依次在匿名函数中进行处理,如果不是nextSuccessor,它们会通过return,一层层的抛出结果。

使用AOP面向切面编程很简单也很巧妙的实现了职责链模式,但是如果链条太长的话也会对性能有影响,因为它会一层层的嵌套函数,直到将整个链条的函数都嵌套完毕。

改造之前的文件上传对象

var getActiveUploadObj = function() {
    try {
        return new ActiveXObject("TXFTNActiveX.FTNUpload"); // IE 上传控件
    } catch (e) {
        return 'nextSuccessor';
    }
};

var getFlashUploadObj = function() {
    if (supportFlash()) {
        var str = '<object type="application/x-shockwave-flash"></object>';
        return $(str).appendTo($('body'));
    }
    return 'nextSuccessor';
};

var getFormUpladObj = function() {
    return $('<form><input name="file" type="file"/></form>').appendTo($('body'));
};

var getUploadObj = getActiveUploadObj.after(getFlashUploadObj).after(getFormUpladObj);

console.log(getUploadObj());

使用这种方式也能很方便的获取到文件上传对象,如果是迭代器的话,还得单独封装一个运行迭代器的函数。

分类: JavaScript设计模式与开发实践 标签: JavaScript模式职责链模式

评论

暂无评论数据

暂无评论数据

目录