JavaScript 设计模式学习笔记(五)

六月 7, 20261读书笔记JavaScript

很早之前看《JavaScript 设计模式与开发实践》整理的笔记,把这些笔记搬到这里,仅供个人学习记录使用,如有需要请支持正版图书,侵删。

职责链模式

职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

实际开发中的职责链模式

假设我们负责一个售卖手机的电商网站,经过分别交纳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优惠券
职责链模式实现
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('手机库存不足');
  }
};

// 定义一个构造函数Chain,在newChain的时候传递的参数即为需要被包装的函数,
// 同时它还拥有一个实例属性this.successor,表示在链中的下一个节点

// Chain.prototype.setNextSuccessor  指定在链中的下一个节点
// Chain.prototype.passRequest  传递请求给某个节点

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;
};

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); // 输出:手机库存不足

职责链模式的优缺点

优点:
  • 使用了职责链模式之后,链中的节点对象可以灵活地拆分重组
  • 可以手动指定起始节点,请求并不是非得从链中的第一个节点开始传递
缺点:
  • 不能保证某个请求一定会被链中的节点处理
  • 职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去

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); // 输出:普通购买,无优惠券
用职责链模式获取文件上传对象
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());

中介者模式

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。

中介者模式的例子

泡泡堂游戏
function Player(name) {
  this.name = name;
  this.enemy = null; // 敌人
}

Player.prototype.win = function () {
  console.log(this.name + 'won');
};

Player.prototype.lose = function () {
  console.log(this.name + 'lost');
};

Player.prototype.die = function () {
  this.lose();
  this.enemy.win();
};

var player1 = new Player('皮蛋');
var player2 = new Player('小乖');

player1.enemy = player2;
player2.enemy = player1;

player1.die(); // 输出:皮蛋lost、小乖won
用中介者模式改造泡泡堂游戏
function Player(name, teamColor) {
  this.name = name; // 角色名字
  this.teamColor = teamColor; // 队伍颜色
  this.state = 'alive'; // 玩家生存状态
}

Player.prototype.win = function () {
  console.log(this.name + 'won');
};

Player.prototype.lose = function () {
  console.log(this.name + 'lost');
};

/*******************玩家死亡*****************/

Player.prototype.die = function () {
  this.state = 'dead';
  playerDirector.reciveMessage('playerDead', this); // 给中介者发送消息,玩家死亡
};

/*******************移除玩家*****************/

Player.prototype.remove = function () {
  playerDirector.reciveMessage('removePlayer', this); // 给中介者发送消息,移除一个玩家
};
/*******************玩家换队*****************/

Player.prototype.changeTeam = function (color) {
  playerDirector.reciveMessage('changeTeam', this, color); // 给中介者发送消息,玩家换队
};

var playerFactory = function (name, teamColor) {
  var newPlayer = new Player(name, teamColor); // 创造一个新的玩家对象
  playerDirector.reciveMessage('addPlayer', newPlayer); // 给中介者发送消息,新增玩家

  return newPlayer;
};

var playerDirector = (function () {
  var players = {}, // 保存所有玩家
    operations = {}; // 中介者可以执行的操作

  /****************新增一个玩家***************************/
  operations.addPlayer = function (player) {
    var teamColor = player.teamColor; // 玩家的队伍颜色
    players[teamColor] = players[teamColor] || []; // 如果该颜色的玩家还没有成立队伍,则
    新成立一个队伍;
    players[teamColor].push(player); // 添加玩家进队伍
  };

  /****************移除一个玩家***************************/
  operations.removePlayer = function (player) {
    var teamColor = player.teamColor, // 玩家的队伍颜色
      teamPlayers = players[teamColor] || []; // 该队伍所有成员
    for (var i = teamPlayers.length - 1; i >= 0; i--) {
      // 遍历删除
      if (teamPlayers[i] === player) {
        teamPlayers.splice(i, 1);
      }
    }
  };

  /****************玩家换队***************************/
  operations.changeTeam = function (player, newTeamColor) {
    // 玩家换队
    operations.removePlayer(player); // 从原队伍中删除
    player.teamColor = newTeamColor; // 改变队伍颜色
    operations.addPlayer(player); // 增加到新队伍中
  };

  operations.playerDead = function (player) {
    // 玩家死亡
    var teamColor = player.teamColor,
      teamPlayers = players[teamColor]; // 玩家所在队伍

    var all_dead = true;

    for (var i = 0, player; (player = teamPlayers[i++]); ) {
      if (player.state !== 'dead') {
        all_dead = false;
        break;
      }
    }

    if (all_dead === true) {
      // 全部死亡
      for (var i = 0, player; (player = teamPlayers[i++]); ) {
        player.lose(); // 本队所有玩家lose
      }

      for (var color in players) {
        if (color !== teamColor) {
          var teamPlayers = players[color]; // 其他队伍的玩家
          for (var i = 0, player; (player = teamPlayers[i++]); ) {
            player.win(); // 其他队伍所有玩家win
          }
        }
      }
    }
  };

  var reciveMessage = function () {
    var message = Array.prototype.shift.call(arguments); // arguments的第一个参数为消息名称
    operations[message].apply(this, arguments);
  };

  return {
    reciveMessage: reciveMessage,
  };
})();
中介者模式是迎合迪米特法则的一种实现。
迪米特法则也叫最少知识原则,是指一个对象应该尽可能少地了解另外的对象。
中介者模式可以非常方便地对模块或者对象进行解耦,但对象之间并非一定需要解耦。
一般来说,如果对象之间的复杂耦合确实导致调用和维护出现了困难,而且这些耦合度随项目的变化呈指数增长曲线,那我们就可以考虑用中介者模式来重构代码。
优点:
中介者模式使各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系。各个对象只需关注自身功能的实现,对象之间的交互关系交给了中介者对象来实现和维护。
缺点:
最大的缺点是系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的。中介者对象自身往往就是一个难以维护的对象。

装饰者模式

对象动态地增加职责的方式称为装饰者(decorator)模式。装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责。

传统面向对象语言的装饰者模式

假设我们在编写一个飞机大战的游戏,随着经验值的增加,我们操作的飞机对象可以升级成更厉害的飞机,一开始这些飞机只能发射普通的子弹,升到第二级时可以发射导弹,升到第三级时可以发射原子弹。
var Plane = function () {};

Plane.prototype.fire = function () {
  console.log('发射普通子弹');
};

var MissileDecorator = function (plane) {
  this.plane = plane;
};

MissileDecorator.prototype.fire = function () {
  this.plane.fire();
  console.log('发射导弹');
};

var AtomDecorator = function (plane) {
  this.plane = plane;
};

AtomDecorator.prototype.fire = function () {
  this.plane.fire();
  console.log('发射原子弹');
};

// 这种给对象动态增加职责的方式,并没有真正地改动对象自身,
// 而是将对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。
// 这些对象都拥有相同的接口(fire方法),当请求达到链中的某个对象时,
// 这个对象会执行自身的操作,随后把请求转发给链中的下一个对象。

var plane = new Plane();
plane = new MissileDecorator(plane);
plane = new AtomDecorator(plane);

plane.fire();
// 分别输出:发射普通子弹、发射导弹、发射原子弹

JavaScript 的装饰者

JavaScript 语言动态改变对象相当容易,我们可以直接改写对象或者对象的某个方法,并不需要使用“类”来实现装饰者模式
var plane = {
  fire: function () {
    console.log('发射普通子弹');
  },
};

var missileDecorator = function () {
  console.log('发射导弹');
};

var atomDecorator = function () {
  console.log('发射原子弹');
};

var fire1 = plane.fire;

plane.fire = function () {
  fire1();
  missileDecorator();
};

var fire2 = plane.fire;

plane.fire = function () {
  fire2();
  atomDecorator();
};

plane.fire();
// 分别输出:发射普通子弹、发射导弹、发射原子弹

用AOP装饰函数

Function.prototype.before = function (beforefn) {
  var __self = this; // 保存原函数的引用
  return function () {
    // 返回包含了原函数和新函数的"代理"函数
    beforefn.apply(this, arguments); // 执行新函数,且保证this不被劫持,新函数接受的参数
    // 也会被原封不动地传入原函数,新函数在原函数之前执行
    return __self.apply(this, arguments); // 执行原函数并返回原函数的执行结果,
    // 并且保证this不被劫持
  };
};

Function.prototype.after = function (afterfn) {
  var __self = this;
  return function () {
    var ret = __self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  };
};

window.onload = function () {
  alert(1);
};

window.onload = (window.onload || function () {})
  .after(function () {
    alert(2);
  })
  .after(function () {
    alert(3);
  })
  .after(function () {
    alert(4);
  });
上面的AOP实现是在Function.prototype上添加beforeafter方法,但许多人不喜欢这种污染原型的方式,那么我们可以做一些变通,把原函数和新函数都作为参数传入before或者after方法
var before = function (fn, beforefn) {
  return function () {
    beforefn.apply(this, arguments);
    return fn.apply(this, arguments);
  };
};

var a = before(
  function () {
    alert(3);
  },
  function () {
    alert(2);
  }
);

a = before(a, function () {
  alert(1);
});
a();

装饰者模式和代理模式

装饰者模式和代理模式的结构看起来非常相像,这两种模式都描述了怎样为对象提供一定程度上的间接引用,它们的实现部分都保留了对另外一个对象的引用,并且向那个对象发送请求。
代理模式和装饰者模式最重要的区别在于它们的意图和设计目的。
  • 代理模式的目的是,当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。
  • 装饰者模式的作用就是为对象动态加入行为。
代理模式强调一种关系(Proxy与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确定对象的全部功能时。代理模式通常只有一层代理-本体的引用,而装饰者模式经常会形成一条长长的装饰链。
END

评论

欢迎分享你的看法

发表评论
欢迎留言,请友好互动
点击添加表情:

全部评论

0

还没有评论

来说两句吧!