状态模式
状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
例子:电灯状态的切换,按同一个键会依次出现弱光、强光、关灯。
// OffLightState:
var OffLightState = function (light) {
this.light = light;
};
OffLightState.prototype.buttonWasPressed = function () {
console.log('弱光'); // offLightState 对应的行为
this.light.setState(this.light.weakLightState); // 切换状态到 weakLightState
};
// WeakLightState:
var WeakLightState = function (light) {
this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function () {
console.log('强光'); // weakLightState 对应的行为
this.light.setState(this.light.strongLightState); // 切换状态到 strongLightState
};
// StrongLightState:
var StrongLightState = function (light) {
this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function () {
console.log('关灯'); // strongLightState 对应的行为
this.light.setState(this.light.offLightState); // 切换状态到 offLightState
};
接下来改写 Light 类,现在不再使用一个字符串来记录当前的状态,而是使用更加立体化的状态对象。我们在 Light 类的构造函数里为每个状态类都创建一个状态对象,这样一来我们可以很明显地看到电灯一共有多少种状态,代码如下:
var Light = function () {
this.offLightState = new OffLightState(this);
this.weakLightState = new WeakLightState(this);
this.strongLightState = new StrongLightState(this);
this.button = null;
};
在 button 按钮被按下的事件里,Context 也不再直接进行任何实质性的操作,而是通过self.currState.buttonWasPressed() 将请求委托给当前持有的状态对象去执行,代码如下:
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 方法,状态对象可以通过这个方法来切换 light对象的状态。前面已经说过,状态的切换规律事先被完好定义在各个状态类中。在 Context中再也找不到任何一个跟状态切换相关的条件分支语句:
Light.prototype.setState = function (newState) {
this.currState = newState;
};
现在可以进行一些测试:
var light = new Light();
light.init();
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); // 把请求委托给 FSM 状态机
}
};
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 () { // 将客户的操作委托给 delegation 对象
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();