场景

假设我们负责一个售卖手机的电商网站,经过分别交纳 500 元定金和 200 元定金的两轮预定 后(订单已在此时生成),现在已经到了正式购买的阶段。 公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过 500 元定金的用 户会收到 100 元的商城优惠券,200 元定金的用户可以收到 50 元的优惠券,而之前没有支付定金 的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。

  1. var order = function( orderType, pay, stock ){
  2. if ( orderType === 1 ){ // 500 元定金购买模式
  3. if ( pay === true ){ // 已支付定金
  4. console.log( '500 元定金预购, 得到 100 优惠券' );
  5. }else{ // 未支付定金,降级到普通购买模式
  6. if ( stock > 0 ){ // 用于普通购买的手机还有库存
  7. console.log( '普通购买, 无优惠券' );
  8. }else{
  9. console.log( '手机库存不足' );
  10. }
  11. }
  12. }
  13. else if ( orderType === 2 ){ // 200 元定金购买模式
  14. if ( pay === true ){
  15. console.log( '200 元定金预购, 得到 50 优惠券' );
  16. }else{
  17. if ( stock > 0 ){
  18. console.log( '普通购买, 无优惠券' );
  19. }else{
  20. console.log( '手机库存不足' );
  21. }
  22. }
  23. }
  24. else if ( orderType === 3 ){
  25. if ( stock > 0 ){
  26. console.log( '普通购买, 无优惠券' );
  27. }else{
  28. console.log( '手机库存不足' );
  29. }
  30. }
  31. };
  32. order( 1 , true, 500); // 输出: 500 元定金预购, 得到 100 优惠券

用职责链模式重构代码

先把 500 元订单、200 元订单以及普通购买分成 3 个函数。 接下来把 orderType、pay、stock 这 3 个字段当作参数传递给 500 元订单函数,如果该函数不 符合处理条件,则把这个请求传递给后面的 200 元订单函数,如果 200 元订单函数依然不能处理 该请求,则继续传递请求给普通购买函数,代码如下:

  1. // 500 元订单
  2. var order500 = function( orderType, pay, stock ){
  3. if ( orderType === 1 && pay === true ){
  4. console.log( '500 元定金预购, 得到 100 优惠券' );
  5. }else{
  6. order200( orderType, pay, stock ); // 将请求传递给 200 元订单
  7. }
  8. }
  9. // 200元订单
  10. var order200 = function(orderType,pay,stock){
  11. if(orderType===2 && pay === true){
  12. console.log( '200 元定金预购, 得到 50 优惠券' );
  13. }else{
  14. orderNormal(orderType,pay,stock)
  15. }
  16. }
  17. var orderNormal = function(orderType,pay,stock){
  18. if(srock > 0){
  19. console.log('common buyer , no discount')
  20. }else {
  21. console.log('stock is not enough')
  22. }
  23. }
  24. // test result
  25. order500( 1 , true, 500); // 输出:500 元定金预购, 得到 100 优惠券
  26. order500( 1, false, 500 ); // 输出:普通购买, 无优惠券
  27. order500( 2, true, 500 ); // 输出:200 元定金预购, 得到 500 优惠券
  28. order500( 3, false, 500 ); // 输出:普通购买, 无优惠券
  29. order500( 3, false, 0 ); // 输出:手机库存不足

可以看到,执行结果和前面那个巨大的 order 函数完全一样,但是代码的结构已经清晰了很 多,我们把一个大函数拆分了 3 个小函数,去掉了许多嵌套的条件分支语句。 目前已经有了不小的进步,但我们不会满足于此,虽然已经把大函数拆分成了互不影响的 3 个小函数,但可以看到,请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函 数之中:

  1. var order500 = function( orderType, pay, stock ){
  2. if ( orderType === 1 && pay === true ){
  3. console.log( '500 元定金预购, 得到 100 优惠券' );
  4. }else{
  5. order200( orderType, pay, stock );
  6. // order200 和 order500 耦合在一起
  7. }
  8. };

灵活可拆分的职责链节点

  1. var order500= function(orderType,pay,stock){
  2. if(orderType === 1 && pay === true){
  3. console.log( '500 元定金预购, 得到 100 优惠券' );
  4. }else{
  5. return 'nextSuccessor' // 我不知道下一个节点是谁,反正把请求往后面传递
  6. }
  7. }
  8. var order200= function(orderType,pay,stock){
  9. if(orderType === 2 && pay === true){
  10. console.log( '200 元定金预购, 得到 50 优惠券' );
  11. }else{
  12. return 'nextSuccessor' // 我不知道下一个节点是谁,反正把请求往后面传递
  13. }
  14. }
  15. var orderNormal= function(orderType,pay,stock){
  16. if(stock>0){
  17. console.log( '普通购买,无优惠券' );
  18. }else{
  19. console.log( '手机库存不足' );
  20. }
  21. }
  22. // 定义构造函数 Chain
  23. var Chain = function(fn){
  24. this.fn = fn
  25. this.successor = null
  26. }
  27. Chain.prototype.setNextSuccessor = function(successor){
  28. return this.successor = successor
  29. }
  30. Chain.prototype.passRequest = function(){
  31. var ret = this.fn.apply(this,arguments)
  32. if(ret === 'nextSuccessor'){
  33. return this.successor && this.successor.passRequest.apply(this.successor,arguments)
  34. }
  35. return ret
  36. }
  37. // 现在我们把 3 个订单函数分别包装成职责链的节点:
  38. var chainOrder500 = new Chain(order500)
  39. var chainOrder200 = new Chain(order200)
  40. var chainOrderNormal = new Chain(orderNormal)
  41. // 指定节点在职责链中的顺序
  42. chainOrder500.setNextSuccessor(chainOrder200)
  43. chainOrder200.setNextSuccessor(chainNormal)
  44. // 把请求传递给第一个节点
  45. chainOrder500.passRequest( 1, true, 500 ); // 输出:500 元定金预购,得到 100 优惠券
  46. chainOrder500.passRequest( 2, true, 500 ); // 输出:200 元定金预购,得到 50 优惠券
  47. chainOrder500.passRequest( 3, true, 500 ); // 输出:普通购买,无优惠券
  48. chainOrder500.passRequest( 1, false, 0 ); // 输出:手机库存不足

思考

对于常用的if esle 来说,虽然简单,但是对与复杂条件需要写各种判断逻辑,造成代码冗余。类似于策略模式,之职责链模式,先对要执行的操作进程单独封装,然后根据业务逻辑组织程一条链,从而实现任务从上而下去执行,后期如果有其它需求 ,则代码的修改成本较小

异步的职责链

让每个节点函数同步返回一个特定的值”nextSuccessor”,来表示 是否把请求传递给下一个节点。而在现实开发中,我们经常会遇到一些异步的问题,比如我们要在 节点函数中发起一个 ajax异步请求,异步请求返回的结果才能决定是否继续在职责链中 passRequest。 这时候让节点函数同步返回”nextSuccessor”已经没有意义了,所以要给 Chain 类再增加一个原型方法 Chain.prototype.next,表示手动传递请求给职责链中的下一个节点:

  1. Chain.protoType.next = function(){
  2. return this.successor && this.successor.passRequest.apply(this.successor,arguments)
  3. }