这个是例子超级长的模式,而且里面的逻辑兜来兜去的跳,需要多花点时间理解。
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变。
定义
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类
实现
- 将状态封装成独立的类,并将请求委托给当前的状态对象,当对象的内部状态改变时,会带来不同的行为变化。
- 使用委托
思想
状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部
优点
- 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里。通过增加新的状态类,很容易增加新的状态和转换。
- 避免 Context 无限膨胀,状态切换的逻辑被分布在状态类中,也去掉了 Context 中原本过多的条件分支。
- 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然。
- Context 中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响。
缺点
- 定义许多状态类,往往会带来代码量的增加
- 逻辑分散
优化
有两种选择来管理 state 对象的创建和销毁。
- 第一种是仅当 state 对象被需要时才创建并随后销毁
- 另一种是一开始就创建好所有的状态对象,并且始终不销毁它们。如果 state
对象比较庞大,可以用第一种方式来节省内存,这样可以避免创建一些不会用到的对象
并及时地回收它们。但如果状态的改变很频繁,最好一开始就把这些 state 对象都创建出
来,也没有必要销毁它们,因为可能很快将再次用到它们 - 享元模式
状态模式和策略模式的对比
- 相同点是,它们都有一个上下文、一些策略或者状态类,上下文把请
求委托给这些类来执行。 - 区别:是策略模式中的各个策略类之间是平等又平行的,它们之间没有任何联系,
所以客户必须熟知这些策略类的作用,以便客户可以随时主动切换算法;而在状态模式中,状态
和状态对应的行为是早已被封装好的,状态之间的切换也早被规定完成,“改变行为”这件事情
发生在状态模式内部。对客户来说,并不需要了解这些细节。这正是状态模式的作用所在
例子
有序状态-一个灯光的改变
引子
// 电灯1 开关
// let Light = function() {
// this.state = 'off'
// this.button = null
// }
// Light.prototype.init = function() {
// let button = document.createElement('button')
// self = this
// button.innerHTML = '开关'
// this.button = document.body.appendChild(button)
// this.button.onclick = function() {
// self.buttonWasPressed()
// }
// }
// Light.prototype.buttonWasPressed = function() {
// if( this.state === 'off') {
// console.log('开灯')
// this.state = 'on'
// } else if(this.state === 'on') {
// console.log('关灯')
// this.state = 'off'
// }
// }
// let light = new Light()
// light.init()
// 电灯2 强弱
// let Light = function() {
// this.state = 'off'
// this.button = null
// }
// Light.prototype.init = function() {
// let button = document.createElement('button')
// self = this
// button.innerHTML = '开关'
// this.button = document.body.appendChild(button)
// this.button.onclick = function() {
// self.buttonWasPressed()
// }
// }
// Light.prototype.buttonWasPressed = function() {
// if( this.state === 'off') {
// console.log('弱光')
// this.state = 'weakLight'
// } else if(this.state === 'weakLight') {
// console.log('强光')
// this.state = 'strongLight'
// } else if(this.state === 'strongLight') {
// console.log('关灯')
// this.state = 'off'
// }
// }
// let light = new Light()
// light.init()
// 状态模式, 增加抽象类
// 抽象类State
let State = function() {}
State.prototype.buttonWasPressed = function () { // 确保子类都实现了该接口
throw Error('State的buttonWasPressed 必须被重写')
}
let OffLightState = function (light) {
this.light = light // 传入this
}
OffLightState.prototype = new State()
OffLightState.prototype.buttonWasPressed = function () {
console.log('弱光')
// 其实是使用this的setState来设置this的 curState 。
// this.light.weakLightState 是Light构造函数里面设置的this.weakLightState。
// 2. 设置下一个状态实例
this.light.setState(this.light.weakLightState)
}
let WeakLightState = function (light) {
this.light = light
}
WeakLightState.prototype = new State()
WeakLightState.prototype.buttonWasPressed = function () {
console.log('强光')
this.light.setState(this.light.strongLightState)
}
let StrongLightState = function (light) {
this.light = light
}
StrongLightState.prototype = new State()
StrongLightState.prototype.buttonWasPressed = function () {
console.log('关灯')
this.light.setState(this.light.offLightState)
}
let Light = function () {
this.offLightState = new OffLightState(this)
this.weakLightState = new WeakLightState(this)
this.strongLightState = new StrongLightState(this)
this.button = null
}
Light.prototype.init = function () {
let button = document.createElement('button')
button.innerHTML = '开关'
self = this
this.button = document.body.appendChild(button)
this.curState = this.offLightState
this.button.onclick = function () {
// 所以这里的 buttongWasPressed 是状态的方法
// 1. 调用状态的方法
self.curState.buttonWasPressed()
// 事件委托给这个方法,每个类都需要实现该方法
}
}
// 3. 改变状态实例
Light.prototype.setState = function (state) {
// 这的state是Light构造函数里面的状态实例。
// 所以设置 state 的实质是,改变 curState 指向的类实例。
this.curState = state
}
let light = new Light()
light.init()
// 每次调用buttonWasPressed方法的时候,执行相关逻辑后,都会设置下一个状态对象实例。
// 这是有序的状态机。
// 增加状态即是增加对象和实例以及加上转换下一个状态的环节。
// 总结:我们为每种状态都定义一个状态子类,然后在 Context 中持有这些状态对象的引用,以便把 currState 设置为当前的状态对象
状态模式的两种写法
// let Light = function () {
// this.curState = FSM.off // 初始化一种状态
// this.button = null
// }
// Light.prototype.init = function() {
// let button = document.createElement('button')
// self = this
// button.innerHTML = '关灯'
// this.button = document.body.appendChild(button)
// this.button.onclick = function () {
// self.curState.buttonWasPressed.call(self)
// // 事件委托,设置调用方法为自己,不需要再另外设置setState
// }
// }
// // 面向对象,变量保存为属性
// let FSM = {
// off: {
// buttonWasPressed: function () {
// console.log('关灯')
// this.button.innerHTML = '开灯'
// this.curState = FSM.on
// }
// },
// on: {
// buttonWasPressed: function () {
// console.log('开灯')
// this.button.innerHTML = '关灯'
// this.curState = FSM.off
// }
// }
// }
// let light = new Light()
// light.init()
// 将状态为对象的写法改为对象的属性。并通过call来改变this指向实体本身。
// 将实体的改变封装在状态里面。
// 闭包写法
// 将client的事情委托给delegation处理
let delegate = function(client, delegation) {
return {
buttonPressed: function() {
return delegation.buttonPressed.apply(client, arguments)
}
}
}
let FSM = {
off: {
buttonPressed: function() {
console.log('关灯')
this.button.innerHTML = '开灯'
this.curState = this.onState
}
},
on: {
buttonPressed: function () {
console.log('开灯')
this.button.innerHTML = '关灯'
this.curState = this.offState
}
}
}
let Light = function() {
this.offState = delegate(this, FSM.off)
this.onState = delegate(this, FSM.on)
this.curState = this.offState
this.button = null
}
Light.prototype.init = function () {
let button = document.createElement('button')
let self = this
button.innerHTML = '关灯'
this.button = document.body.appendChild(button)
this.button.onclick = function () {
self.curState.buttonPressed()
}
}
let light = new Light()
light.init()
分开独立的状态-文件上传按钮的状态
引子
// 文件上传
window.external.upload = function (state) {
console.log(state)
}
let plugin = (function() {
let 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
})()
let Upload = function (fileName) {
this.plugin = plugin
this.fileName = fileName
this.button1 = null
this.button2 = null
this.state = 'sign'
}
Upload.prototype.init = function () {
let 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()
}
Upload.prototype.bindEvent = function () {
let self = this
// 上传按钮
this.button1.onclick = function () {
if (self.state === 'sign') {
console.log('扫描中。点击无效...')
} else if (self.state === 'uploading') {
self.changeState('parse')
} else if (self.state === 'parse') {
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.uploading()
this.dom.parentNode.removeChild(this.dom)
console.log('删除完成')
break
default:
break;
}
this.state = state
}
let uploadObj = new Upload('js')
uploadObj.init()
window.external.upload = function (state) {
uploadObj.changeState(state)
}
window.external.upload('sign')
setTimeout(function () {
window.external.upload('uploading')
}, 1000)
setTimeout(function () {
window.external.upload('done')
}, 5000)
使用状态模式优化上传文件功能
// 文件上传
window.external.upload = function (state) {
console.log(state)
}
let plugin = (function () {
let 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
})()
let Upload = function (fileName) {
this.plugin = plugin
this.fileName = fileName
this.button1 = null
this.button2 = null
// this.state = 'sign'
// 1. 新增状态类
this.signState = new SignState(this)
this.uploadingState = new UploadingState(this)
this.pauseState = new PauseState(this)
this.doneState = new DoneState(this)
this.errorState = new ErrorState(this)
this.curState = this.signState // 2. 设置初始值
}
Upload.prototype.init = function () {
let 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()
}
Upload.prototype.bindEvent = function () {
let self = this
// 3. 将请求委托给状态类来执行
// 上传按钮
this.button1.onclick = function () {
self.curState.clickHandler1()
// if (self.state === 'sign') {
// console.log('扫描中。点击无效...')
// } else if (self.state === 'uploading') {
// self.changeState('parse')
// } else if (self.state === 'parse') {
// self.changeState('uploading')
// } else if (self.state === 'done') {
// console.log('上传完成,点击无效...')
// } else if (self.state === 'error') {
// console.log('上传失败,点击无效')
// }
}
// 删除按钮
this.button2.onclick = function () {
self.curState.clickHandler2()
// 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('文件正在上传中,不能删除...')
// }
}
}
// 4. 新增状态对应的行为
Upload.prototype.sign = function () {
this.plugin.sign()
this.curState = this.signState
}
Upload.prototype.uploading = function () {
this.button1.innerHTML = '正在上传,点击暂停'
this.plugin.uploading()
this.curState = this.uploadingState
}
Upload.prototype.pause = function () {
this.button1.innerHTML = '已暂停,点击继续上传'
this.plugin.pause()
this.curState = this.pauseState
}
Upload.prototype.done = function () {
this.button1.innerHTML = '上传完成'
this.plugin.done()
this.curState = this.doneState
}
Upload.prototype.error = function () {
this.button1.innerHTML = '上传失败'
this.curState = this.errorState
}
Upload.prototype.del = function () {
this.plugin.del()
this.dom.parentNode.removeChild(this.dom)
}
// 5. 去掉更改状态的方法
// 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.uploading()
// this.dom.parentNode.removeChild(this.dom)
// console.log('删除完成')
// break
// default:
// break;
// }
// this.state = state
// }
// 6. 新建状态工厂函数
let StateFactory = (function () {
const State = function () {}
State.prototype.clickHandler1 = function () {
throw Error('子类必须继承父类的clickHandler1方法')
}
State.prototype.clickHandler2 = function () {
throw Error('子类必须继承父类的clickHandler2方法')
}
return function (params) {
let F = function (uploadObj) {
this.uploadObj = uploadObj // this.uploadObj 指向传入的this,即Upload函数
}
F.prototype = new State()
for (const key in params) {
if (Object.hasOwnProperty.call(params, key)) {
F.prototype[key] = params[key]
}
}
return F
}
})()
// 7. 新建状态类,更新事情行为。
// 每个状态都返回带有两个属性的对象,属性分别绑定了对应的事件。状态函数执行Upload本身的方法,改变行为。
let SignState = StateFactory({
clickHandler1() {
console.log('扫描中,点击无效...')
},
clickHandler2() {
console.log('文件正在上传中,不能删除...')
},
})
let UploadingState = StateFactory({
clickHandler1() {
this.uploadObj.pause()
},
clickHandler2() {
console.log('文件正在上传中,不能删除...')
},
})
let PauseState = StateFactory({
clickHandler1() {
this.uploadObj.uploading()
},
clickHandler2() {
this.uploadObj.del()
},
})
let DoneState = StateFactory({
clickHandler1() {
console.log('文件上传完成,点击无效')
},
clickHandler2() {
this.uploadObj.del()
},
})
let ErrorState = StateFactory({
clickHandler1() {
console.log('文件上传失败,点击无效')
},
clickHandler2() {
this.uploadObj.del()
},
})
let uploadObj = new Upload('js')
uploadObj.init()
window.external.upload = function (state) {
// uploadObj.changeState(state)
uploadObj[state]()
}
window.external.upload('sign')
setTimeout(function () {
window.external.upload('uploading')
}, 1000)
setTimeout(function () {
window.external.upload('done')
}, 5000)
// 更改状态从而更改点击事情的行为。
游戏中的状态模式
频繁操作动作改变状态。收集动作后一次执行。
class SuperMarry {
constructor() {
this._currentState = [];
this.states = {
jump() {
console.log("跳跃");
},
move() {
console.log("移动");
},
shoot() {
console.log("射击");
},
squat() {
console.log("蹲下");
},
};
}
change(arr) {
this._currentState = arr;
return this;
}
go() {
console.log("触发动作");
this._currentState.forEach((T) => this.states[T] && this.states[T]());
return this;
}
}
const SuperCls = new SuperMarry()
SuperCls.change(["jump", "move"]).go().change(["jump"]).go();
SuperCls.change(["jump", "move", "squat"]).go();