[TOC]
# 状态模式
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。
## 初识状态模式
状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,只需要在上下文中,把某个请求委托给当前的状态对象即可,该状态对象会福州渲染它自身的行为。
```javascript
// offLightState
var OffLightState = function(light){
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){
console.log('弱光') // offLightState 对应的行为
this.light.setState(this.light.weekLightState); // 切换状态到 weekLightState
};
// WeekLightState
var WeekLightState = function(light){
this.light = light;
};
WeekLightState.prototype.buttonWasPressed = function(){
console.log('强光')
this.light.setState(this.light.strongLightState);
};
// StrongLightState
var StrongLightState = function(light){
this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function(){
console.log('关灯')
this.light.setState(this.light.offLightState);
};
// Light 类
var Light = function(){
this.offLightState = new OffLightState(this);
this.weekLightState = new WeekLightState(this);
this.strongLightState = new StrongLightState(this);
this.button = null;
};
Light.prototype.init = function(){
var button = document.createElement('button');
var 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();
```
状态模式可以使每一种状态和它对应的行为之间的关系局部化,这些行为被分散和封装在各自对应的状态类中。
## 状态模式的定义
GOF中定义:*允许一个对象在其内部状态改变时改变它的行为,对象开起来似乎改变了它的类*。
- 将状态封装成独立的类,并将请求委托给当前的状态对象,当对象的内部状态改变时,会带来不同的行为变化。
- 使用的对象,在不同的状态下具有截然不同的行为,这个对象看起来是从不同的类中实例化而来的,实际上是使用了委托的效果。
## 状态模式的通用结构
首先定义了`Ligth`类,`Light`类在这里也被称为上下文(`Context`)。随后在`Light`的构造函数中,我们要创建每一个状态类的实例对象,`Context`将持有这些状态对象的引用,以便把请求委托给状态对象。
```javascript
// Light 类
var Light = function(){
this.offLightState = new OffLightState(this);
this.weekLightState = new WeekLightState(this);
this.strongLightState = new StrongLightState(this);
this.button = null;
};
Light.prototype.init = function(){
var button = document.createElement('button');
var self = this;
this.button = document.body.appendChild(button);
this.button.innerHTML = '开关';
this.currState = this.offLightState;
this.button.onclick = function(){
self.currState.buttonWasPressed();
};
};
```
最后再编写各种状态类,`ligth`对象被传入状态类的构造函数,状态对象也需要持有`ligth`对象的引用,以便调用`ligth`中的方法或者直接操作`light`对象
```javascript
// offLightState
var OffLightState = function(light){
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){
console.log('弱光') // offLightState 对应的行为
this.light.setState(this.light.weekLightState); // 切换状态到 weekLightState
};
```
## 缺少抽象类的变通方式
与模板方法模式中相同,让抽象父类的抽象方法直接抛出一个异常。
```javascript
var State = function(){};
State.prototype.buttonWasPressed = function(){
throw new Error('父类的 buttonWasPressed 方法必须被重写');
};
var SuperStrongLigthState = function(light){
this.light = light;
};
SuperStrongLigthState.prototype = new State(); // 继承抽象父类
SuperStrongLigthState.prototype.buttonWasPressed = function(){
....
};
```
## 另一个状态模式示例 -- 文件上传
第一步提供`window.external.upload`函数,在页面中模拟上传插件
```javascript
window.external.upload = function(state){
console.log(state) // 可能是sign,uploading,done,error
};
var plugin = (function(){
var plugin = document.createElement('embed');
plugin.style.display = 'none';
plugin.type = 'application/txfn-webkit';
plugin.sign = function(){
...
};
plugin.pause = function(){
...
};
plugin.uploading = function(){
...
};
plugin.del = function(){
...
};
plugin.done = function(){
...
};
document.body.appendChild(plugin);
return plugin;
})();
```
第二步编写`Upload`函数
```javascript
var Upload = function(filename){
this.plugin = plugin;
this.fileName = filename;
this.button1 = null;
this.button2 = null;
this.signState = new SignState(this); // 设置初始状态为waiting
this.uploadingState = new UploadingState(this);
this.pauseState = new PauseState(this);
this.doneState = new DoneState(this);
this.errorState = new ErrorState(this);
this.currState = this.signState; // 设置当前状态
}
```
第三步创建`Upload.prototype.init`
```javascript
Upload.prototype.init = function(){
var that = this;
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();
};
```
第四步负责具体的按钮事件实现,在点击了按钮之后,Context 并不做任何具体的操作,而是把请求委托给当前的状态类来执行
```javascript
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.plugin.uploading();
this.currState = this.uploadingState;
};
Upload.prototype.pause = function(){
this.plugin.pasue();
this.currState = this.pauseState;
};
Upload.prototype.done = function(){
this.plugin.done();
this.currState = this.doneState;
};
Upload.prototype.error = function(){
this.plugin.error();
this.currState = this.errorState;
};
Upload.prototype.del = function(){
this.plugin.del();
this.currState = this.delState;
};
```
第五步编写各个状态类的实现
```javascript
var StateFactory = (function(){
var State = function(){};
State.prototype.clickHanlder1 = function(){
throw new Error('子类必须重写父类的方法');
}
State.prototype.clickHanlder2 = function(){
throw new Error('子类必须重写父类的方法');
}
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({
clickHandle1: function(){
console.log(...)
},
clickHandle2: function(){
console.log(...)
}
});
var UploadingState = StateFactory({
clickHandle1: function(){
this.uploadObj.pause();
},
clickHandle2: function(){
console.log(...)
}
});
// 其它的状态同理
...
```
测试
```javascript
var uploadObj = new Upload('xxx');
uploadObj.init();
window.external.upload = function(state){
uploadObj[state]();
};
window.external.upload('sigin')
```
## 状态模式的优缺点
优点:
- 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。
- 避免 Context 无限膨胀,状态切换和逻辑被分布在状态类中,也去掉了 Context 中原本过多的条件分支。
- 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然。
- Context 中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响。
缺点:
会在系统中定义许多的状态类,而且系统中会因此增加不少对象,由于逻辑分散在状态类中,也造成了逻辑分散的问题,无法在一个地方就看出整个状态机的逻辑。
## 状态模式中的性能优化点
可以在仅当 state 对象被需要时才创建并销毁,另一种是一开始就创建好所有的状态对象,并且始终不销毁它们。
## 状态模式和策略模式的关系
策略模式和状态模式的相同点是,它们都有一个上下文、一些策略或者状态类,上下文把请求委托给这些类来执行。
它们之间的区别是策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,所以客户必须熟知这些策略类的作用,以便客户随时可以主动切换算法;而在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情在状态内部。
## JavaScript版本的状态机
```javascript
var delegate = function(client, delegation){
return {
buttonWasPressed: function(){ // 将客户的操作类委托给 delegation 对象
return delegation.buttonWasPressed.apply(client, argument);
}
}
};
var FSM = {
off: {
buttonWasPressed: function(){
console.log(...);
this.currState = this.onState;
}
},
on: {
buttonWasPressed: function(){
console.log(...);
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;
this.button = document.body.appendChild(button);
this.button.click = function(){
self.currState.buttonWasPressed();
}
};
var light = new Light();
light.init();
```
## 表驱动的有限状态机
可以在标准很清楚地看到下一个状态是由当前状态和行为共同决定的,这样一来,我们就可以在表中查找状态,而不必定义很多条件分支。
[https://github.com/jakesgordon/javascript-state-machine/](https://github.com/jakesgordon/javascript-state-machine/)