状态模式是一种非同寻常的优秀模式,他也许是解决某些需求场景的最好方法
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。下面来看一个电灯程序,代码如下:
javascript
class Light{
constructor(){
this.state='off'; // 给电灯设置初始状态off
this.button=null; // 电灯开关按钮
}
init(){
const button=document.createElement('button');
button.innerHTML='开关';
this.button=document.body.appendChild(button);
this.button.onclick=()=>{
this.buttonWasPressed();
}
}
buttonWasPressed(){
if(this.state==='off'){
console.log('开灯');
this.state='on';
}else if(this.state==='on'){
console.log('关灯');
this.state='off';
}
}
}
const light=new Light();
light.init();
上面是一个反例,现在我们使用状态模式改进电灯的程序。通常我们谈到封装,一般都会优先封装对象的行为,而不是对象的状态。但在状态模式正好相反,状态模式是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,所以button按钮被按下的时候,只需要在上下文中,把这个请求委托给当前的状态对象即可,该状态对象会负责渲染他自身的行为。
状态模式改进电灯程序,添加弱光,强光两种状态
javascript
// OffLightState
class OffLightState{
constructor(light){
this.light=light;
}
buttonWasPressed(){
console.log('弱光');
this.light.setState(this.light.weakLightState);
}
}
// WeakLightState
class WeakLightState{
constructor(light){
this.light=light;
}
buttonWasPressed(){
console.log('强光');
this.light.setState(this.light.strongLightState);
}
}
// StrongLightState
class StrongLightState{
constructor(light){
this.light=light;
}
buttonWasPressed(){
console.log('关灯');
this.light.setState(this.light.offLightState);
}
}
class Light{
constructor(){
this.offLightState = new OffLightState(this);
this.weakLightState = new WeakLightState(this);
this.strongLightState = new StrongLightState(this);
this.button = null;
}
init(){
const button=document.createElement('button');
button.innerHTML='开关';
this.button=document.body.appendChild(button);
this.currState = this.offLightState; //设置当前状态
this.button.onclick=()=>{
this.currState.buttonWasPressed();
}
}
setState(newState){
this.currState=newState;
}
}
const light=new Light();
light.init();
如果后面需要加超强光,超超强光状态,就只需要添加新的状态类就可以了
通过电灯的例子,相信我们对于状态模式已经有了一定程度的了解。现在回头看GoF中对状态模式的定义:允许一个对象在其内部状态改变时改变他的行为,对象看起来似乎修改了他的类。
这句话第一部分的意思是将状态封装成独立的类,并将请求委托给当前对象,当对象的内部状态改变时,会带来不同的行为变化。电灯的例子足以说明这一点,在off和on这两种不同状态需啊,我们点击同一个按钮,得到的行为反馈是截然不同的。
第二部分是从客户的角度来啦吗,我们使用的对象,在不同的状态下具有截然不同的行为,这个对象看起来是从不同的类中实例化而来的,实际上这是使用了委托的效果。
文件上传的例子
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/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'; // 设置初始状态为waiting
}
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.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);
this.button1.innerHTML = '删除完成';
break;
}
this.state=state;
};
var uploadObj = new Upload('Javascript设计模式与开发实践');
uploadObj.init();
window.external.upload=function(state){ // 插件调用javascript的方法
uploadObj.changeState(state);
};
window.external.upload('sign'); // 文件开始扫描
setTimeout(function(){
window.external.upload('uploading'); // 2秒后开始上传
},2000);
setTimeout(function(){
window.external.upload('done'); // 9秒后上传完成
},9000);
状态模式重构文件上传代码
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/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); // 设置初始状态为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 = 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.errorState;
}
Upload.prototype.del=function(){
this.plugin.done();
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){ // 插件调用javascript的方法
uploadObj[state]();
};
window.external.upload('sign'); // 文件开始扫描
setTimeout(function(){
window.external.upload('uploading'); // 2秒后开始上传
},2000);
setTimeout(function(){
window.external.upload('done'); // 9秒后上传完成
},9000);
非原创,来源javascript设计模式与开发实践 -曾探