一、基础介绍
状态模式:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
- 将状态封装成独立的类,并将请求委托给当前状态对象。
- 对象具有状态变化
- 不同的状态,对应不同的行为。
- 状态有共同的行为方法, Context 会将请求委托给状态对象的这些方法,但是行为方法的内部实现是不同的。
二、例子讲解
以 promise 为例,我们知道 promise 是对异步操作的封装,内部有 3 种状态,每种状态对应不同的行为。
以下的代码采用状态模式
实现了简单版的 promise 。
注意 then
方法并未提供完整功能,没有继续返回 promise ,所以无法链式调用。
思考步骤:
- 通常 Promise 使用关键字 new 调用,所以可以用构造函数/类来模拟。
- 根据 Promise/A+ 规范,Promise 需要一个 executor , executor 是一个函数,有两个参数,分别是
resolve
、reject
- 构造函数需要做的事情
- then 方法参数中的回调处理不是立刻调用,而是等异步操作完成之后才会执行
- 状态变化不可逆
// 有限状态机(Finite State Machine)
const FSM = {
pending: {
name: 'pending',
done() {
this.state = FSM.pending;
console.log('pending 中...');
}
},
resolved: {
name: 'resolved',
done() {
if (this.state.name !== 'pending') return;
this.state = FSM.resolved;
console.log('状态更改为 resolved');
// 调用 onResolve
this.onResolve();
}
},
rejected: {
name: 'rejected',
done() {
if (this.state.name !== 'pending') return;
this.state = FSM.rejected;
console.log('状态更改为 rejected');
// 调用 onReject
this.onReject();
}
}
}
class myPromise {
constructor(executor) {
// 初始化状态为 pendding
this.state = FSM.pending;
FSM.pending.done.call(this);
// 异步操作回调函数初始化
this.onResolve = Function.prototype;
this.onReject= Function.prototype;
// 执行 executor
executor(() => {
FSM.resolved.done.call(this);
},() => {
FSM.rejected.done.call(this);
})
}
then(onResolve, onReject) {
// 校验 onResolve
if (onResolve && typeof onResolve !== 'function') {
throw new Error("出错啦~");
}
// 校验 onReject
if (onReject && typeof onReject !== 'function') {
throw new Error("出错啦~");
}
this.onResolve = typeof onResolve === 'function' ? onResolve : () => {};
this.onReject = typeof onReject=== 'function' ? onReject : () => { throw new Error("出错啦~");};
}
}
测试代码:
// 测试代码
function ajax(type=true) {
const promise = new myPromise((resolve, reject) => {
setTimeout(() => {
if (type) {
resolve();
} else {
reject();
}
}, 1000);
});
return promise;
}
const response_1 = ajax();
response_1.then(()=>{
console.log('response_1 请求成功后的处理');
},()=>{
console.log('response_1 请求失败后的处理');
});
const response_2 = ajax(false);
response_2.then(()=>{
console.log('response_2 请求成功后的处理');
},()=>{
console.log('response_2 请求失败后的处理');
});
测试代码运行结果
状态变更不可逆的处理
if (this.state.name !== 'pending') return;
状态变更是不可逆的
github 上的 JavaScript 有限状态机库
链接:jakesgordon/javascript-state-machine
三、应用场景
红绿灯、灯的开关、文件上传、游戏中任务的动作状态等。
四、优缺点
优点:
- 避免多重条件分支语句
- 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。易扩展,添加新的状态。
-
Context 中的请求动作
和状态类中封装的行为
可以独立变化而互不影响
缺点:
- 编写多个状态类。
- 逻辑分散在状态类中。
五、状态模式中的性能优化
- state 对象的创建和销毁
- 状态对象被需要时,才动态创建。
- 开始就创建所有的状态对象。
- 利用享元模式,可以使个 Context 对象共享 state 对象。
六、策略模式和状态模式的关系
相同点:
- 它们都有一个上下文、一些策略/状态类。上下文把请求委托给这些类来执行。
不同点:
- 策略模式重点在于封装不同的算法,算法之间没有强联系,可互相替换。
- 状态模式重点在于封装状态,行为会放在状态内部,状态之间会发生变化。
推荐阅读 设计模式之禅:策略模式VS状态模式
参考
《JavaScript 设计模式与开发实践》曾探
《JavaScript 设计模式》张容铭
Javascript设计模式系统讲解与应用
网友评论