javascript设计模式--状态模式(State)
1 <!doctype html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>State Pattern</title> 6 </head> 7 <body> 8 9 <script> 10 /** 11 * 状态模式 12 * 13 * 定义: 14 * 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。 15 * 16 * 本质: 17 * 根据状态来分离和选择行为 18 * 19 * 1.状态和行为 20 * 所谓对象的状态,通常指的就是对象实例的属性的值;而行为指的就是对象的功能,再具体点,行为大多可以对应到方法上。 21 * 状态模式的功能就是分离状态和行为,通过维护状态的变化,来调用不同状态的的不同功能。 22 * 也就是说,状态和行为是相关联的,它们的关系可以描述为:状态决定行为。 23 * 24 * 2.行为的平行性 25 * 平行性指的是各个状态的行为所处的层次是一样的,相互是独立的,没有关联的,是根据不同的状态来决定到底走平行线哪一条。行为是不用的,当然对应的实现也是不同的,相互之间是不可替换的。 26 * 平等性强调的是可替换性,大家是同一行为的不同描述或实现,因此在同一个行为发生的时候,可以根据条件挑选任意的一个实现来进行相应的处理。 27 * 状态模式和策略模式的结构完全一样。状态模式的行为是平行性的,不可相互替换的;而策略模式的行为是平等性的,可相互替换的。 28 * 29 * 3.上下文和状态处理对象呢 30 * 在状态模式中,上下文是持有状态的对象,但是上下文自身并不处理跟状态相关的行为,而是把处理状态的功能委托给了状态对应的状态处理类来处理。 31 * 在具体的状态处理类中经常需要获取上下文自身的数据,甚至在必要的时候会回调上下文的方法,因此,通常将上下文自身当作一个参数传递给具体的状态处理类。 32 * 客户端一般只和上下文交互。客户端可以用状态对象来配置一个上下文,一旦配置完毕,就不需要再和状态对象打交道了。 33 * 34 * 4.不完美的OCP体验 35 * 由于状态的维护和转换在状态模式结构里面,不管你是扩展了状态实现类,还是新添加了状态实现类,都需要修改状态维护和转换的地方。 36 * 37 * 5.创建和销毁状态对象 38 * 究竟何时创建和销毁状态对象? 39 * 1)当需要使用状态对象的时候创建,使用完后销毁它们 40 * 2)提前创建它们并始终不销毁。 41 * 3)采用延迟加载和缓存合用的方式,就是当第一次需要使用状态对象的时候创建,使用完后并不销毁对象,而是把这个对象缓存起来,等待下一次使用,而且在合适的时候,会有缓存框剪销毁状态对象。 42 * 如果状态在运行时是不可知的,而且上下文比较稳定,建议选择1. 43 * 如果状态改变很频繁,而且状态对象还存储着大量的数据信息,建议选择2. 44 * 如果无法确定状态改变是否频繁,而且有些状态对象的状态数据量大,有些较小,建议选择3. 45 * 46 * 6.状态模式的调用顺序 47 * 在Context中进行状态的维护和转换: 48 * 1)调用上下文的方法来处理业务请求。 49 * 2)判断并维护状态。 50 * 3)根据状态来调用相应的状态处理对象的方法。 51 * 52 * 采用让状态对象来维护和转换状态的调用顺序 53 * 1)调用上下文的方法来处理业务请求。 54 * 2)获取State对象。 55 * 3)委托让相应的状态对象去处理。 56 * 4)调用构造方法得到下一个状态对象的实例。 57 * 5)设置下一个状态处理对象。 58 * 6)再到6),直到结束。 59 * 60 * 状态的维护和转换控制 61 * 所谓状态的维护,指的是维护状态的数据,给状态设置不用的状态值;而状态的转换,指的是根据状态的变化来选择不用的状态处理对象。在状态模式中,通常有两个地方可以进行状态的维护和转换控制。 62 * 一个就是在上下文中。因为状态本身通常被实现为上下文对象的状态,因此可以在上下文中进行状态维护,当然也就可以控制状态的转换了。 63 * 另外一个地方就是在状态的处理类中。当每个状态处理对象处理完自身状态所对应的功能后,可以根据需要指定后继状态,以便让应用能正确处理后继的请求。 64 * 65 * 如何选择这两种方式? 66 * 1.如果状态转换的规则是一定的,一般不需要进行什么扩展规则,那么就适合在上下文中统一进行状态的维护。 67 * 2.如果状态的转换取决于前一个状态动态处理的结果,或者是依赖于外部数据,为了增强灵活性,这种情况下,一般是在状态处理类中进行状态的维护。 68 * 69 * 还可以使用数据库来维护状态 70 * 71 */ 72 73 (function () { 74 // 示例代码 75 76 // 实现一个与Context的一个特定状态相关的行为 77 function ConcreteStateA() {} 78 79 ConcreteStateA.prototype.handle = function () {}; 80 81 function ConcreteStateB() {} 82 83 ConcreteStateB.prototype.handle = function () {}; 84 85 // 定义客户感兴趣的接口,通常会维护一个State的对象实例 86 function Context(state) { 87 this.state = state; 88 } 89 90 Context.prototype = { 91 request: function (param) { 92 this.state.handle(param); 93 } 94 }; 95 }()); 96 97 (function () { 98 // 示例 99 100 function NormalVoteState() {} 101 102 NormalVoteState.prototype.vote = function (user, voteItem, voteManager) { 103 voteManager.mapVote[user] = voteItem; 104 }; 105 106 function RepeatVoteState() {} 107 108 RepeatVoteState.prototype.vote = function (user, voteItem, voteManager) { 109 console.log('请不要重复投票'); 110 }; 111 112 function SpliteVoteState() {} 113 114 SpliteVoteState.prototype.vote = function (user, coteItem, voteManager) { 115 var s = voteManager.mapVote[user]; 116 if (s) { 117 delete voteManager.mapVote[user]; 118 } 119 120 console.log('你有恶意刷票行为,取消投票资格'); 121 }; 122 123 function BlackVoteState() {} 124 125 BlackVoteState.prototype.vote = function (user, voteItem, voteManager) { 126 console.log('进入黑名单,将禁止登录和使用本系统'); 127 }; 128 129 function VoteManager() { 130 this.state = null; 131 this.mapVote = {}; 132 this.mapVoteCount = {}; 133 } 134 135 VoteManager.prototype = { 136 vote: function (user, voteItem) { 137 var oldVoteCount = this.mapVoteCount[user] || 0; 138 139 this.mapVoteCount[user] = ++oldVoteCount; 140 141 if (oldVoteCount == 1) { 142 this.state = new NormalVoteState(); 143 } else if (oldVoteCount > 1 && oldVoteCount < 5) { 144 this.state = new RepeatVoteState(); 145 } else if (oldVoteCount >= 5 && oldVoteCount < 8) { 146 this.state = new SpliteVoteState(); 147 } else if (oldVoteCount >= 8) { 148 this.state = new BlackVoteState(); 149 } 150 151 this.state.vote(user, voteItem, this); 152 } 153 }; 154 155 var vm = new VoteManager(); 156 for (var i = 0; i < 8; i++) { 157 vm.vote('u1', 'A'); 158 } 159 160 161 // another 162 var States = { 163 normal: function (user, voteItem, voteManager) { 164 voteManager.mapVote[user] = voteItem; 165 }, 166 repeat: function (user, voteItem, voteManager) { 167 console.log('请不要重复投票'); 168 }, 169 splite: function (user, coteItem, voteManager) { 170 var s = voteManager.mapVote[user]; 171 if (s != null) { 172 delete voteManager.mapVote[user]; 173 } 174 175 console.log('你有恶意刷票行为,取消投票资格'); 176 }, 177 black: function (user, voteItem, voteManager) { 178 console.log('进入黑名单,将禁止登录和使用本系统'); 179 } 180 }; 181 182 function VoteManager2() { 183 this.state = null; 184 this.mapVote = {}; 185 this.mapVoteCount = {}; 186 } 187 188 VoteManager2.prototype = { 189 vote: function (user, voteItem) { 190 var oldVoteCount = this.mapVoteCount[user] || 0; 191 192 this.mapVoteCount[user] = ++oldVoteCount; 193 194 var state; 195 if (oldVoteCount == 1) { 196 state = 'normal'; 197 } else if (oldVoteCount > 1 && oldVoteCount < 5) { 198 state = 'repeat'; 199 } else if (oldVoteCount >= 5 && oldVoteCount < 8) { 200 state = 'splite'; 201 } else if (oldVoteCount >= 8) { 202 state = 'black'; 203 } 204 205 this.state = States[state]; 206 207 this.state(user, voteItem, this); 208 } 209 }; 210 211 var vm = new VoteManager2(); 212 for (var i = 0; i < 8; i++) { 213 vm.vote('u1', 'A'); 214 } 215 }()); 216 217 (function () { 218 // 在状态处理类中进行后继状态的维护和转换 219 220 function NormalVoteState() { 221 this.vote = function (user, voteItem, voteManager) { 222 voteManager.mapVote[user] = voteItem; 223 console.log('恭喜你投票成功'); 224 // 正常投票完毕,维护下一个状态,同一个人再投票就重复了 225 voteManager.mapState[user] = new RepeatVoteState(); 226 }; 227 } 228 229 function RepeatVoteState() { 230 this.vote = function (user, voteItem, voteManager) { 231 console.log('请不要重复投票'); 232 if (voteManager.mapVoteCount[user] >= 4) { 233 voteManager.mapState[user] = new SpliteVoteState(); 234 } 235 }; 236 } 237 238 function SpliteVoteState() { 239 this.vote = function (user, voteItem, voteManager) { 240 var s = voteManager.mapVote[user]; 241 242 if (s != null) { 243 delete voteManager.mapVote[user]; 244 } 245 246 console.log('你有恶意刷票行为,取消投票资格'); 247 248 if (voteManager.mapVoteCount[user] >= 7) { 249 voteManager.mapState[user] = new BlackVoteState(); 250 } 251 }; 252 } 253 254 function BlackVoteState() { 255 this.vote = function (user, voteItem, voteManager) { 256 console.log('进入黑名单,将禁止登录和使用本系统'); 257 }; 258 } 259 260 function VoteManager() { 261 this.mapState = {}; 262 this.mapVote = {}; 263 this.mapVoteCount = {}; 264 265 this.vote = function (user, voteItem) { 266 var oldVoteCount = this.mapVoteCount[user]; 267 268 if (oldVoteCount == null) { 269 oldVoteCount = 0; 270 } 271 this.mapVoteCount[user] = ++oldVoteCount; 272 273 var state = this.mapState[user]; 274 if (state == null) { 275 state = new NormalVoteState(); 276 } 277 278 state.vote(user, voteItem, this); 279 }; 280 } 281 282 283 var vm = new VoteManager(); 284 for (var i = 0; i < 8; i++) { 285 vm.vote('u1', 'A'); 286 } 287 288 // another way 289 290 function VoteManager2() { 291 var mapState = {}; 292 var mapVote = {}; 293 var mapVoteCount = {}; 294 295 this.vote = function (user, voteItem) { 296 var oldVoteCount = mapVoteCount[user]; 297 298 if (oldVoteCount == null) { 299 oldVoteCount = 0; 300 } 301 mapVoteCount[user] = ++oldVoteCount; 302 303 var state = mapState[user]; 304 if (state == null) { 305 state = voteNormal; 306 } 307 308 state(user, voteItem); 309 }; 310 311 function voteNormal(user, voteItem) { 312 mapVote[user] = voteItem; 313 console.log('恭喜你投票成功'); 314 // 正常投票完毕,维护下一个状态,同一个人再投票就重复了 315 return mapState[user] = voteRepeat; 316 } 317 318 function voteRepeat(user, voteItem) { 319 console.log('请不要重复投票'); 320 if (mapVoteCount[user] >= 4) { 321 return mapState[user] = voteSplite; 322 } 323 } 324 325 function voteSplite(user, voteItem) { 326 var s = mapVote[user]; 327 328 if (s != null) { 329 delete mapVote[user]; 330 } 331 332 console.log('你有恶意刷票行为,取消投票资格'); 333 334 if (mapVoteCount[user] >= 7) { 335 return mapState[user] = voteBlack; 336 } 337 } 338 339 function voteBlack(user, voteItem) { 340 console.log('进入黑名单,将禁止登录和使用本系统'); 341 } 342 } 343 344 var vm = new VoteManager2(); 345 for (var i = 0; i < 8; i++) { 346 vm.vote('u1', 'A'); 347 } 348 }()); 349 350 (function () { 351 // 模拟工作流 352 /* 353 请假流程,需项目经理和部门经理审批 354 */ 355 356 // 公共状态处理机 357 function LeaveRequestContext() { 358 // 持有一个状态对象 359 this.state = null; 360 // 包含流程处理需要的业务数据对象 361 this.businessVO = null; 362 } 363 364 LeaveRequestContext.prototype = { 365 // 执行工作 366 doWork: function () { 367 this.state.doWork(this); 368 } 369 }; 370 371 // 定义请假单的业务数据模型 372 function LeaveRequestModel() { 373 // 请假人 374 this.user = ''; 375 // 请假开始时间 376 this.beginDate = ''; 377 // 请假天数 378 this.leaveDays = ''; 379 // 审核结果 380 this.result = ''; 381 } 382 383 function ProjectManagerState() { 384 this.doWork = function (request) { 385 var leaveRequestModel = request.businessVO; 386 387 console.log('项目经理审核中,请稍候。。'); 388 console.log(leaveRequestModel.user + '申请从' 389 + leaveRequestModel.beginDate + '开始请假' 390 + leaveRequestModel.leaveDays + '天,请项目经理审核(1为同意,2为不同意)'); 391 392 var answer = window.prompt('1为同意,2为不同意'); 393 var result = answer == 1 ? '同意' : '不同意'; 394 leaveRequestModel.result = '项目经理审核结果:' + result; 395 396 if (answer == 1) { 397 if (leaveRequestModel.leaveDays > 3) { 398 request.state = new DepManagerState(); 399 } else { 400 request.state = new AuditOverState(); 401 } 402 } else { 403 request.state = new AuditOverState(); 404 } 405 406 request.doWork(); 407 }; 408 } 409 410 function DepManagerState() { 411 this.doWork = function (request) { 412 var leaveRequestModel = request.businessVO; 413 414 console.log('部门经理审核中,请稍候。。'); 415 console.log(leaveRequestModel.user + '申请从' 416 + leaveRequestModel.beginDate + '开始请假' 417 + leaveRequestModel.leaveDays + '天,请项目经理审核(1为同意,2为不同意)'); 418 419 var answer = window.prompt('1为同意,2为不同意'); 420 var result = answer == 1 ? '同意' : '不同意'; 421 leaveRequestModel.result = '部门经理审核结果:' + result; 422 423 request.state = new AuditOverState(); 424 425 request.doWork(); 426 }; 427 } 428 429 function AuditOverState() { 430 this.doWork = function (request) { 431 var leaveRequestModel = request.businessVO; 432 // do sth 433 console.log(leaveRequestModel.user + ',你的请假申请已经审核结束,结果是:' + leaveRequestModel.result); 434 }; 435 } 436 437 var lrm = new LeaveRequestModel(); 438 lrm.user = '小林'; 439 lrm.beginDate = '2014-4-2'; 440 lrm.leaveDays = 5; 441 442 var request = new LeaveRequestContext(); 443 request.businessVO = lrm; 444 request.state = new ProjectManagerState(); 445 446 request.doWork(); 447 448 449 // another 450 451 function LeaveRequestContext2() { 452 this.state = null; 453 // 包含流程处理需要的业务数据对象 454 this.businessVO = null; 455 this.doWork = function () { 456 if (typeof this.state == 'function') { 457 this.state = this.state(this); 458 this.doWork(); 459 } 460 }; 461 } 462 463 function projectManagerState(request) { 464 var leaveRequestModel = request.businessVO; 465 466 console.log('项目经理审核中,请稍候。。'); 467 console.log(leaveRequestModel.user + '申请从' 468 + leaveRequestModel.beginDate + '开始请假' 469 + leaveRequestModel.leaveDays + '天,请项目经理审核(1为同意,2为不同意)'); 470 471 var answer = window.prompt('1为同意,2为不同意'); 472 var result = answer == 1 ? '同意' : '不同意'; 473 leaveRequestModel.result = '项目经理审核结果:' + result; 474 475 var state; 476 if (answer == 1) { 477 if (leaveRequestModel.leaveDays > 3) { 478 state = depManagerState; 479 } else { 480 state = auditOverState; 481 } 482 } else { 483 state = auditOverState; 484 } 485 486 return state; 487 } 488 489 function depManagerState(request) { 490 var leaveRequestModel = request.businessVO; 491 492 console.log('部门经理审核中,请稍候。。'); 493 console.log(leaveRequestModel.user + '申请从' 494 + leaveRequestModel.beginDate + '开始请假' 495 + leaveRequestModel.leaveDays + '天,请项目经理审核(1为同意,2为不同意)'); 496 497 var answer = window.prompt('1为同意,2为不同意'); 498 var result = answer == 1 ? '同意' : '不同意'; 499 leaveRequestModel.result = '部门经理审核结果:' + result; 500 501 return auditOverState; 502 } 503 504 function auditOverState(request) { 505 var leaveRequestModel = request.businessVO; 506 // do sth 507 console.log(leaveRequestModel.user + ',你的请假申请已经审核结束,结果是:' + leaveRequestModel.result); 508 } 509 510 var lrm = new LeaveRequestModel(); 511 lrm.user = '小林'; 512 lrm.beginDate = '2014-4-2'; 513 lrm.leaveDays = 5; 514 515 var request = new LeaveRequestContext2(); 516 request.businessVO = lrm; 517 request.state = projectManagerState; 518 519 request.doWork(); 520 521 }()); 522 523 /* 524 何时选用状态模式 525 1.如果一个对象的行为取决于它的状态,而且它必须在运行时刻根据状态来改变它的行为,可以使用状态模式把状态和行为分离。 526 2.如果一个操作中含有庞大的多分支语句,而且这些分支依赖于该对象的状态,可以使用状态模式,把各个分支的处理分散包装到单独的对象处理类中。 527 528 529 相关模式 530 531 状态模式和策略模式 532 两个结构相同。状态模式的行为是平行性的,不可相互替换的;而策略模式的行为是平等性的,可相互替换的。 533 534 状态模式和观察者模式 535 相似但又有区别,可以组合使用。 536 这两个模式都是在状态发生改变的时候触发行为,只不过观察者模式的行为是固定的,那就是通知所有观察者;而状态模式是根据状态来选择不同的处理。 537 从表面来看,两个模式相似,观察者模式中的被观察对象就好比状态模式中的上下文,观察者模式中当被观察对象的状态发生改变的时候,触发的通知所有观察者的方法就好比状态模式中,根据状态的变化选择对应的状态处理。 538 但实际这两个模式是不同的,观察者模式的目的是在被观察者的状态发生改变的时候,触发观察联动,具体如何处理观察者模式不管;而状态模式的主要目的在于根据状态来分离和选择行为,当状态发生改变的时候,动态地改变行为。 539 这两个模式可以组合使用,比如在观察者模式的观察者部分,当被观察对象的状态发生改变,触发通知了所有的观察者后,使用状态模式根据通知过来的状态选择相应的处理。 540 541 状态模式和单例模式 542 可以组合使用 543 把状态模式中的状态处理类实现成单例。 544 545 状态模式和享元模式 546 可以组合使用 547 由于状态模式把状态对应的行为分散到多个状态对象中,会造成很多细粒度的状态对象,可以把这些状态处理对象通过享元模式来共享,从而节省资源。 548 */ 549 550 (function () { 551 // http://www.dofactory.com/javascript-state-pattern.aspx 552 var TrafficLight = function () { 553 554 var count = 0; 555 var currentState = new Red(this); 556 557 this.change = function (state) { 558 // limits number of changes 559 if (count++ >= 10) return; 560 561 currentState = state; 562 currentState.go(); 563 }; 564 565 this.start = function () { 566 currentState.go(); 567 }; 568 } 569 570 var Red = function (light) { 571 this.light = light; 572 573 this.go = function () { 574 log.add("Red --> for 1 minute"); 575 light.change(new Green(light)); 576 } 577 }; 578 579 var Yellow = function (light) { 580 this.light = light; 581 582 this.go = function () { 583 log.add("Yellow --> for 10 seconds"); 584 light.change(new Red(light)); 585 } 586 }; 587 588 var Green = function (light) { 589 this.light = light; 590 591 this.go = function () { 592 log.add("Green --> for 1 minute"); 593 light.change(new Yellow(light)); 594 } 595 }; 596 597 // log helper 598 599 var log = (function () { 600 var log = ""; 601 return { 602 add: function (msg) { log += msg + "\n"; }, 603 show: function () { 604 alert(log); 605 log = ""; 606 } 607 } 608 })(); 609 610 function run() { 611 612 var light = new TrafficLight(); 613 light.start(); 614 615 log.show(); 616 } 617 }()); 618 </script> 619 </body> 620 </html>