状态模式
2022年9月29日大约 3 分钟
状态模式
状态模式-文件上传
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 方法无需改变,仍然负责往页面中创建跟上传流程有关的DOM 节点,并开始绑定按钮的事件:
Upload.prototype.init = function () {
var that = this;
this.dom = document.createElement('div');
this.dom.innerHTML =
`<span>文件名称:${this.fileName}</span>
< button data - action="button1" > 扫描中</ >
<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 并不做任何具体的操作,而是把请求委托给当前的状态类来执行:
Upload.prototype.bindEvent = function () {
var self = this;
this.button1.onclick = function () {
self.currState.clickHandler1();
}
this.button2.onclick = function () {
self.currState.clickHandler2();
}
};
// 把状态对应的逻辑行为放在 Upload 类中:
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.del();
this.dom.parentNode.removeChild(this.dom);
};
// 工作略显乏味,我们要编写各个状态类的实现。值得注意的是,我们使用了StateFactory,从而避免因为 JavaScript 中没有抽象类所带来的问题。
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');
// 1 秒后开始上传
}, 1000);
setTimeout(function () {
window.external.upload('done');
// 2 秒后上传完成
}, 2000);
小结
状态模式的优点如下:
状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。
避免 Context 无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了 Context 中原本过多的条件分支。
用对象代替字符串来记录当前状态,使得状态的切换更加一目了然。
Context 中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响。
状态模式的缺点:
会在系统中定义许多状态类,编写 20 个状态类是一项枯燥乏味的工作,而且系统中会因此而增加不少对象。另外,由于逻辑分散在状态类中,虽然避开了不受欢迎的条件分支语句,但也造成了逻辑分散的问题,我们无法在一个地方就看出整个状态机的逻辑。