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>

 

你可能感兴趣的:(JavaScript)