设计模式之状态模式

Posted by franki on November 10, 2018

state

状态模式的关键是区分事物内部的状态,事物的内部状态改变往往会带来事物行为的变化。 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

一 电灯程序

// 普通处理方式
var Light = function() {
   this.state = "off"; // 电灯初始状态
   this.button = null; // 电灯的开关按钮
};

Light.prototype.init = function() {
   var button = document.createElement("button"),
   self = this;

   button.innerHTML = "开关";
   this.button = document.body.appendChild(button);
   this.button.onclick = function() {
      self.buttonWasPressed();
   };
};

Light.prototype.buttonWasPressed = function() {
   if (this.state === "off") {
      console.log("弱光");
      this.state = "weakLight";
   } else if (this.state === "weakLight") {
      console.log("强光");
      this.state = "strongLight";
   } else {
      console.log("关灯");
      this.state = "off";
   }
};

var light = new Light();
light.init();

以上程序是违反开放-封闭原则的,每次新增或者修改light的状态,都需要改动buttonWasPressed方法中的代码。状态的切换非常不明显,堆砌了了很多的if else语句, 造成难以阅读和维护。

// 状态模式改进
var State = function() {};

// 抽象方法buttonWasPressed
State.prototype.buttonWasPressed = function() {
   throw new Error("父类的buttonWasPressed方法必须被重写");
};

// offLightState
var OffLightState = function(light) {
   this.light = light;
};

OffLightState.prototype = new State(); // 继承抽象父类

OffLightState.prototype.buttonWasPressed = function() {
   console.log("弱光");
   this.light.setState(this.light.weakLightState);
};

// WeakLightState

var WeakLightState = function(light) {
   this.light = light;
};

WeakLightState.prototype = new State(); // 继承抽象父类

WeakLightState.prototype.buttonWasPressed = function() {
   console.log("强光");
   this.light.setState(this.light.strongLightState);
};

// StrongLightState

var StrongLightState = function(light) {
   this.light = light;
};

StrongLightState.prototype = new State(); // 继承抽象父类

StrongLightState.prototype.buttonWasPressed = function() {
   console.log("超强光");
   this.light.setState(this.light.superLightState);
};

// SuperLightState

var SuperLightState = function(light) {
   this.light = light;
};

SuperLightState.prototype = new State();

SuperLightState.prototype.buttonWasPressed = function() {
   console.log("关灯");
   this.light.setState(this.light.offLightState);
};

// Context 上下文
var Light = function() {
   this.offLightState = new OffLightState(this); // 持有状态对象的引用
   this.weakLightState = new WeakLightState(this);
   this.strongLightState = new StrongLightState(this);
   this.superLightState = new SuperLightState(this);
   this.button = null;
};

Light.prototype.init = function() {
   var button = document.createElement("button"),
   self = this;

   this.button = document.body.appendChild(button);
   this.button.innerHTML = "开关";

   this.currState = this.offLightState; // 设置默认的初始状态

   this.button.onclick = function() {
      // 定义用户的请求动作
      self.currState.buttonWasPressed();
   };
};

Light.prototype.setState = function(newState) {
   this.currState = newState;
};

var light = new Light();
light.init();
// 文件上传
var plugin = (function() {
   var plugin = document.createElement("embed");
   plugin.style.display = "none";

   plugin.type = "application/txftn-webkit";

   plugin.sign = function() {
      console.log("开始文件扫描");
   };

   plugin.pause = function() {
      console.log("暂停文件上传");
   };

   plugin.uploading = function() {
      console.log("开始文件上传");
   };

   plugin.del = function() {
      console.log("删除文件上传");
   };

   plugin.done = function() {
      console.log("文件上传完成");
   };

   document.body.appendChild(plugin);

   return plugin;
})();

var Upload = function(fileName) {
   this.plugin = plugin;
   this.fileName = fileName;
   this.button1 = null;
   this.button2 = null;
   this.state = "sign";
};

Upload.prototype.init = function() {
   this.dom = document.createElement("div");
   this.dom.innerHTML =
   "<span>文件名称:" +
   this.fileName +
   '</span>\
   <button data-action="button1">扫描中</button>\
   <button data-action="button2">删除</button>';

   document.body.appendChild(this.dom);
   this.button1 = this.dom.querySelector('[data-action="button1"]');
   this.button2 = this.dom.querySelector('[data-action="button2"]');
   this.bindEvent();
};

Upload.prototype.bindEvent = function() {
   var self = this;
   this.button1.onclick = function() {
      if (self.state === "sign") {
         console.log("扫描中,点击无效");
      } else if (self.state === "uploading") {
         self.changeState("pause");
      } else if (self.state === "pause") {
         self.changeState("uploading");
      } else if (self.state === "done") {
         console.log("文件已完成上传,点击无效");
      } else if (self.state === "error") {
         console.log("文件上传失败,点击无效");
      }
   };

   this.button2.onclick = function() {
      if (
         self.state === "done" ||
         self.state === "error" ||
         self.state === "pause"
      ) {
         self.changeState("del");
      } else if (self.state === "sign") {
         console.log("文件正在扫描中,不能删除");
      } else if (self.state === "uploading") {
         console.log("文件正在上传中,不能删除");
      }
   };
};

Upload.prototype.sign = function() {
   this.plugin.sign();
   this.currState = this.signState;
};

Upload.prototype.uploading = function() {
   this.button1.innerHTML = "正在上传,点击暂停";
   this.plugin.uploading();
   this.currState = this.uploadingState;
};

Upload.prototype.pause = function() {
   this.button1.innerHTML = "已暂停,点击继续上传";
   this.plugin.pause();
   this.currState = this.pauseState;
};

Upload.prototype.done = function() {
   this.button1.innerHTML = "上传完成";
   this.plugin.done();
   this.currState = this.doneState;
};

Upload.prototype.error = function() {
   this.button1.innerHTML = "上传失败";
   this.currState = this.errrorState;
};

Upload.prototype.del = function() {
   this.plugin.del();
   this.dom.parentNode.removeChild(this.dom);
};

Upload.prototype.changeState = function(state) {
   switch (state) {
      case "sign":
         this.plugin.sign();
         this.button1.innerHTML = "扫描中,任何操作无效";
         break;
      case "uploading":
         this.plugin.uploading();
         this.button1.innerHTML = "正在上传,点击暂停";
         break;
      case "pause":
         this.plugin.pause();
         this.button1.innerHTML = "已暂停,点击继续上传";
         break;
      case "done":
         this.plugin.done();
         this.button1.innerHTML = "上传完成";
         break;
      case "error":
         this.button1.innerHTML = "上传失败";
         break;
      case "del":
         this.plugin.del();
         this.dom.parentNode.removeChild(this.dom);
         console.log("删除完成");
         break;
   }

   this.state = state;
};

var uploadObj = new Upload("Javascript设计模式与开发实践");
uploadObj.init();

window.external.upload = function(state) {
   uploadObj.changeState(state);
};

window.external.upload("sign");

setTimeout(function() {
   window.external.upload("uploading");
}, 1000);

setTimeout(function() {
   window.external.upload("done");
}, 5000);

// 状态模式改写文件上传
var plugin = (function() {
   var plugin = document.createElement("embed");
   plugin.style.display = "none";

   plugin.type = "application/txftn-webkit";

   plugin.sign = function() {
      console.log("开始文件扫描");
   };

   plugin.pause = function() {
      console.log("暂停文件上传");
   };

   plugin.uploading = function() {
      console.log("开始文件上传");
   };

   plugin.del = function() {
      console.log("删除文件上传");
   };

   plugin.done = function() {
      console.log("文件上传完成");
   };

   document.body.appendChild(plugin);

   return plugin;
})();

var Upload = function(fileName) {
   this.plugin = plugin;
   this.fileName = fileName;
   this.button1 = null;
   this.button2 = null;
   this.signState = new SignState(this);
   this.uploadingState = new UploadingState(this);
   this.pauseState = new PauseState(this);
   this.doneState = new DoneState(this);
   this.errrorState = new ErrorState(this);
   this.currState = this.signState;
};

Upload.prototype.init = function() {
   this.dom = document.createElement("div");
   this.dom.innerHTML =
   "<span>文件名称:" +
   this.fileName +
   '</span>\
   <button data-action="button1">扫描中</button>\
   <button data-action="button2">删除</button>';

   document.body.appendChild(this.dom);
   this.button1 = this.dom.querySelector('[data-action="button1"]');
   this.button2 = this.dom.querySelector('[data-action="button2"]');
   this.bindEvent();
};

Upload.prototype.bindEvent = function() {
   var self = this;
   this.button1.onclick = function() {
      self.currState.clickHandler1();
   };

   this.button2.onclick = function() {
      self.currState.clickHandler2();
   };
};

Upload.prototype.sign = function() {
   this.plugin.sign();
   this.currState = this.signState;
};

Upload.prototype.uploading = function() {
   this.button1.innerHTML = "正在上传,点击暂停";
   this.plugin.uploading();
   this.currState = this.uploadingState;
};

Upload.prototype.pause = function() {
   this.button1.innerHTML = "已暂停,点击继续上传";
   this.plugin.pause();
   this.currState = this.pauseState;
};

Upload.prototype.done = function() {
   this.button1.innerHTML = "上传完成";
   this.plugin.done();
   this.currState = this.doneState;
};

Upload.prototype.error = function() {
   this.button1.innerHTML = "上传失败";
   this.currState = this.errrorState;
};

Upload.prototype.del = function() {
   this.plugin.del();
   this.dom.parentNode.removeChild(this.dom);
};

var StateFactory = (function() {
   var State = function() {};
   State.prototype.clickHandler1 = function() {
      throw new Error("子类必须重写父类的clickHandler1方法");
   };

   State.prototype.clickHandler2 = function() {
      throw new Error("子类必须重写父类的clickHandler2方法");
   };

   return function(param) {
      var F = function(uploadObj) {
         this.uploadObj = uploadObj;
      };

      F.prototype = new State();

      for (var i in param) {
         F.prototype[i] = param[i];
      }

      return F;
   };
})();

var SignState = StateFactory({
   clickHandler1: function() {
      console.log("扫描中,点击无效");
   },
   clickHandler2: function() {
      console.log("文件正在上传中,不能删除");
   }
});

var UploadingState = StateFactory({
   clickHandler1: function() {
      this.uploadObj.pause();
   },
   clickHandler2: function() {
      console.log("文件正在上传中,不能删除");
   }
});

var PauseState = StateFactory({
   clickHandler1: function() {
      this.uploadObj.uploading();
   },
   clickHandler2: function() {
      this.uploadObj.del();
   }
});

var DoneState = StateFactory({
   clickHandler1: function() {
      console.log("文件已完成上传,点击无效");
   },
   clickHandler2: function() {
      this.uploadObj.del();
   }
});

var ErrorState = StateFactory({
   clickHandler1: function() {
      console.log("文件上传失败,点击无效");
   },
   clickHandler2: function() {
      this.uploadObj.del();
   }
});

var uploadObj = new Upload("Javascript设计模式与开发实践");
uploadObj.init();

window.external.upload = function(state) {
   uploadObj[state]();
};

window.external.upload("sign");

setTimeout(function() {
   window.external.upload("uploading");
}, 1000);

setTimeout(function() {
   window.external.upload("done");
}, 5000);

// JavaScript 版本的状态机
var Light = function() {
   this.currState = FSM.off;
   this.button = null;
};

Light.prototype.init = function() {
   var button = document.createElement("button"),
   self = this;

   button.innerHTML = "已关灯";
   this.button = document.body.appendChild(button);

   this.button.onclick = function() {
   self.currState.buttonWasPressed.call(self);
   };
};

var FSM = {
   off: {
      buttonWasPressed: function() {
         console.log("关灯");
         this.button.innerHTML = "下一次按我开灯";
         this.currState = FSM.on;
      }
   },
   on: {
      buttonWasPressed: function() {
         console.log("开灯");
         this.button.innerHTML = "下一次按我关灯";
         this.currState = FSM.off;
      }
   }
};

var light = new Light();
light.init();

// delegate 函数 闭包实现状态模式
var delegate = function(client, delegation) {
   return {
      buttonWasPressed: function() {
         return delegation.buttonWasPressed.apply(client, arguments);
      }
   };
};

var FSM = {
   off: {
      buttonWasPressed: function() {
         console.log("关灯");
         this.button.innerHTML = "下一次按我开灯";
         this.currState = this.onState;
      }
   },
   on: {
      buttonWasPressed: function() {
         console.log("开灯");
         this.button.innerHTML = "下一次按我关灯";
         this.currState = this.offState;
      }
   }
};

var Light = function() {
   this.offState = delegate(this, FSM.off);
   this.onState = delegate(this, FSM.on);
   this.currState = this.offState;
   this.button = null;
};

Light.prototype.init = function() {
   var button = document.createElement("button"),
   self = this;

   button.innerHTML = "已关灯";
   this.button = document.body.appendChild(button);

   this.button.onclick = function() {
   self.currState.buttonWasPressed();
   };
};

var light = new Light();
light.init();