设计模式——状态模式, 实现stopwatch
Posted rencoo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式——状态模式, 实现stopwatch相关的知识,希望对你有一定的参考价值。
1.模拟传统面向对象语言的状态模式实现
1// Stopwatch类 状态机
2class Stopwatch {
3 constructor() {
4 this.button1 = null;
5 this.button2 = null;
6
7 this.resetState = new ResetState(this); // 设置初始状态为 reset 重置
8 this.startState = new StartState(this);
9 this.pauseState = new PauseState(this);
10 this.currState = this.resetState; // 设置当前状态
11 }
12
13 /****** 创建DOM节点, 绑定事件 ******/
14 init() {
15 this.dom = document.createElement(‘div‘);
16 this.dom.setAttribute(‘id‘, ‘stopwatch‘);
17 this.dom.innerhtml = `
18 <div class="header">stopwatch</div>
19 <div class="main">
20 <!-- 时间显示部分 -->
21 <div class="display" >00:00.00</div>
22 <!-- 控制部分 -->
23 <div class="ctrl">
24 <button type="button" id=‘btnOne‘ disabled=‘disabled‘>计次</button> <!--class="active"-->
25 <button type="button" id=‘btnTwo‘>启动</button> <!--class="stop"-->
26 </div>
27 <!-- 显示计次(时间间隔) -->
28 <ol class="lap">
29 </ol>
30 </div>`;
31
32 document.body.appendChild(this.dom);
33 this.button1 = this.dom.querySelector(‘#btnOne‘);
34 this.button2 = this.dom.querySelector(‘#btnTwo‘);
35 this.display = this.dom.querySelector(‘.display‘); // 总时间显示
36 this.lap = this.dom.querySelector(‘.lap‘); // 计次显示
37
38 this.bindEvent();
39
40 Stopwatch.addStopwatchListener(t => {
41 this.displayTotalTime(t);
42 });
43 Stopwatch.addStopwatchListener(t => {
44 this.displayLapTime(t);
45 })
46 }
47
48 // 将请求委托给当前状态类来执行
49 bindEvent() {
50 this.button1.addEventListener(‘click‘, () => {
51 this.currState.clickHandler1();
52 }, false);
53 this.button2.addEventListener(‘click‘, () => {
54 this.currState.clickHandler2();
55 }, false);
56 }
57
58 /****** 状态对应的逻辑行为 ******/
59 // 开始
60 start() {
61 if(timer.count === 0) {
62 timer.count = 1;
63 this.insertLap();
64 }
65 // 行为
66 timer.startTimer();
67
68 // 状态
69 this.setState(this.startState);
70
71 // 样式
72 this.setStyle();
73 }
74 // 暂停
75 pause() {
76 // 行为
77 timer.stopTimer();
78
79 // 状态
80 this.setState(this.pauseState);
81
82 // 样式
83 this.setStyle();
84 }
85 // 计次
86 lapf() {
87 // 行为
88 this.insertLap();
89
90 timer.timeAccumulationContainer.push(timer.timeAccumulation);
91 timer.s += timer.timeAccumulationContainer[timer.count - 1];
92 timer.timeAccumulation = 0;
93 timer.count++;
94 }
95 // 重置
96 reset() {
97 // 行为
98 timer.reset();
99
100 // 状态
101 this.setState(this.resetState);
102
103 // 样式
104 this.setStyle();
105 }
106
107 /****** 辅助方法 ******/
108 // 总时间显示(从启动到当前时刻的累积时间)
109 displayTotalTime() {
110 let totaltimeAccumulation = timer.timeAccumulation * 10 + timer.s * 10;
111 this.display.innerHTML = `${Timer.milSecond_to_time(totaltimeAccumulation)}`;
112 }
113 // 计次条目显示
114 displayLapTime() {
115 let li = this.lap.querySelector(‘li‘),
116 spans = li.querySelectorAll(‘span‘),
117 task = spans[0], time = spans[1];
118
119 task.innerHTML = `计次${timer.count}`;
120 time.innerHTML = `${Timer.milSecond_to_time(timer.timeAccumulation * 10)}`;
121 }
122
123 // 设置状态
124 setState(newState) {
125 this.currState = newState;
126 }
127
128 // 设置样式
129 setStyle() {
130 if(this.currState instanceof StartState) {
131 let button1 = this.button1;
132 button1.disabled = ‘‘;
133 button1.innerHTML = ‘计次‘;
134 button1.className = ‘active‘;
135
136 let button2 = this.button2;
137 button2.innerHTML = ‘停止‘;
138 button2.className = ‘stop‘;
139 } else if (this.currState instanceof PauseState) {
140 this.button1.innerHTML = ‘复位‘;
141
142 let button2 = this.button2;
143 button2.innerHTML = ‘启动‘;
144 button2.className = ‘start‘;
145 } else if (this.currState instanceof ResetState) {
146 let button1 = this.button1;
147 button1.disabled = ‘disabled‘;
148 button1.innerHTML = ‘计次‘;
149 button1.className = ‘‘;
150
151 this.display.innerHTML = ‘00:00.00‘;
152 this.lap.innerHTML = ‘‘;
153 }
154 }
155
156 // 插入一个计次
157 insertLap() {
158 let t = Stopwatch.templateLap();
159 this.lap.insertAdjacentHTML(‘afterbegin‘, t);
160 }
161 static templateLap() {
162 return `
163 <li><span></span><span></span></li>
164 `;
165 }
166
167 // 将函数推入回调队列
168 static addStopwatchListener(handler) {
169 timer.stopwatchHandlers.push(handler);
170 }
171}
172
173// 编写各个状态类的实现
174// 模拟State抽象类; 在Java中,所有的状态类必须继承自一个State抽象父类
175class State{
176 constructor() {}
177
178 static clickHandler1() {
179 throw new Error(‘父类的clickHandler1方法必须被重写‘);
180 }
181
182 static clickHandler2() {
183 throw new Error(‘父类的clickHandler2方法必须被重写‘);
184 }
185}
186
187// 状态类
188class ResetState {
189 constructor(stopwatchObj) {
190 this.stopwatchObj = stopwatchObj;
191 }
192
193 static clickHandler1() {
194 console.log(‘初始状态下点击计次无效‘);
195 }
196
197 clickHandler2() {
198 console.log(‘初始状态下点击启动, 切换为启动状态‘);
199
200 //
201 this.stopwatchObj.start();
202 }
203
204}
205ResetState.prototype = new State(); // 继承抽象父类, 用于检测
206
207class StartState {
208 constructor(stopwatchObj) {
209 this.stopwatchObj = stopwatchObj;
210 }
211
212 clickHandler1() {
213 console.log(‘启动状态下点击计次‘);
214
215 //
216 this.stopwatchObj.lapf();
217 }
218
219 clickHandler2() {
220 console.log(‘启动状态下点击暂停, 切换为暂停状态‘);
221
222 //
223 this.stopwatchObj.pause();
224 }
225}
226StartState.prototype = new State(); // 继承抽象父类, 用于检测
227
228class PauseState {
229 constructor(stopwatchObj) {
230 this.stopwatchObj = stopwatchObj;
231 }
232
233 clickHandler1() {
234 console.log(‘暂停状态下点击复位, 切换为初始状态‘);
235
236 //
237 this.stopwatchObj.reset();
238 }
239
240 clickHandler2() {
241 console.log(‘暂停状态下点击启动, 切换为启动状态‘);
242
243 //
244 this.stopwatchObj.start();
245 }
246}
247PauseState.prototype = new State(); // 继承抽象父类, 用于检测
248
249// 时间机器(时间插件)
250class Timer {
251 constructor() {
252 // 计时器
253 this.stopwathchTimer = null;
254 this.count = 0; // 计次的次数
255 this.timeAccumulation = 0; // 累积时长
256 this.timeAccumulationContainer = []; // 存放已经结束的计次的容器
257 this.s = 0; // 已经结束的所有计次累积时间
258 this.stopwatchHandlers = []; // 用于startTimer里回调队列
259 }
260
261 reset() {
262 // 重置
263 this.stopwathchTimer = null;
264 this.count = 0;
265 this.timeAccumulation = 0;
266 this.timeAccumulationContainer = [];
267 this.s = 0;
268 }
269
270 startTimer() {
271 this.stopTimer();
272 this.stopwathchTimer = setInterval(() => {
273 this.timeAccumulation++; // 注意时间累积量 _timeAccumulation 是厘秒级别的(由于显示的是两位)
274 this.stopwatchHandlers.forEach(handler => {
275 handler(this.timeAccumulation);
276 })
277 }, 1000 / 100)
278 }
279
280 stopTimer() {
281 clearInterval(this.stopwathchTimer );
282 }
283
284 // 将时间累积量转化成时间
285 static milSecond_to_time(t) {
286 let time,
287 minute = Timer.addZero(Math.floor(t / 60000) % 60), // 分
288 second = Timer.addZero(Math.floor(t / 1000) % 60), // 秒
289 centisecond = Timer.addZero(Math.floor(t / 10) % 100) ; // 厘秒(百分之一秒)
290 time = `${minute}:${second}.${centisecond}`;
291 return time;
292 }
293 // 修饰器;加零
294 static addZero(t) {
295 t = t < 10 ? ‘0‘ + t : t;
296 return t;
297 }
298}
299const timer = new Timer();
300
301// 测试
302const stopwatchObj = new Stopwatch();
303
304stopwatchObj.init();
305```
2.javascript 版本的状态机
1// Stopwatch类 状态机
2class Stopwatch {
3 constructor() {
4 this.button1 = null;
5 this.button2 = null;
6
7 // JavaScript 版本的状态机
8 this.FSM = {
9 /****** 状态对应的逻辑行为 ******/
10 // btn1无效 / btn2复位 >> 开始
11 reset: {
12 clickHandler1() {
13 // 复位状态下计次按钮无效
14 },
15 clickHandler2() {
16 console.log(‘初始状态下点击启动, 切换为启动状态‘);
17
18 if(timer.count === 0) {
19 timer.count = 1;
20 this.insertLap();
21 }
22 // 行为
23 timer.startTimer();
24
25 // 切换状态
26 this.currState = this.FSM.start;
27
28 // 样式
29 this.setStyle(‘startState‘);
30 }
31 },
32 // bnt1计次 / btn2开始 >> 暂停
33 start: {
34 clickHandler1() {
35 console.log(‘启动状态下点击计次, 不切换状态‘);
36
37 // 行为
38 this.insertLap();
39
40 timer.timeAccumulationContainer.push(timer.timeAccumulation);
41 timer.s += timer.timeAccumulationContainer[timer.count - 1];
42 timer.timeAccumulation = 0;
43 timer.count++;
44
45 // 状态不改变
46
47 // 样式不改变
48 },
49 clickHandler2() {
50 console.log(‘启动状态下点击暂停, 切换为暂停状态‘);
51
52 // 行为
53 timer.stopTimer();
54
55 // 切换状态
56 this.currState = this.FSM.pause;
57
58 // 样式
59 this.setStyle(‘pauseState‘);
60 }
61 },
62 // btn1暂停 >> 重置 / btn2暂停 >> 开始
63 pause: {
64 clickHandler1() {
65 console.log(‘暂停状态下点击复位, 切换为初始状态‘);
66
67 // 行为
68 timer.reset();
69
70 // 切换状态
71 this.currState = this.FSM.reset;
72
73 // 样式
74 this.setStyle(‘resetState‘);
75 },
76 clickHandler2() {
77 console.log(‘暂停状态下点击启动, 切换为启动状态‘);
78
79 // 行为
80 timer.startTimer();
81
82 // 状态
83 this.currState = this.FSM.start;
84
85 // 样式
86 this.setStyle(‘startState‘);
87 }
88 }
89 };
90
91 this.currState = this.FSM.reset; // 设置当前状态
92 }
93
94 /****** 创建DOM节点, 绑定事件 ******/
95 init() {
96 this.dom = document.createElement(‘div‘);
97 this.dom.setAttribute(‘id‘, ‘stopwatch‘);
98 this.dom.innerHTML = `
99 <div class="header">stopwatch</div>
100 <div class="main">
101 <!-- 时间显示部分 -->
102 <div class="display" >00:00.00</div>
103 <!-- 控制部分 -->
104 <div class="ctrl">
105 <button type="button" id=‘btnOne‘ disabled=‘disabled‘>计次</button> <!--class="active"-->
106 <button type="button" id=‘btnTwo‘>启动</button> <!--class="stop"-->
107 </div>
108 <!-- 显示计次(时间间隔) -->
109 <ol class="lap">
110 </ol>
111 </div>`;
112
113 document.body.appendChild(this.dom);
114 this.button1 = this.dom.querySelector(‘#btnOne‘);
115 this.button2 = this.dom.querySelector(‘#btnTwo‘);
116 this.display = this.dom.querySelector(‘.display‘); // 总时间显示
117 this.lap = this.dom.querySelector(‘.lap‘); // 计次显示
118
119 this.bindEvent();
120
121 Stopwatch.addStopwatchListener(t => {
122 this.displayTotalTime(t);
123 });
124 Stopwatch.addStopwatchListener(t => {
125 this.displayLapTime(t);
126 })
127 }
128
129 // 通过 call 方法直接把请求委托给某个字面量对象(FSM)来执行
130 bindEvent() {
131 this.button1.addEventListener(‘click‘, () => {
132 this.currState.clickHandler1.call(this); // 把请求委托给FSM 状态机
133 }, false);
134 this.button2.addEventListener(‘click‘, () => {
135 this.currState.clickHandler2.call(this);
136 }, false);
137 }
138
139 /****** 辅助方法 ******/
140 // 总时间显示(从启动到当前时刻的累积时间)
141 displayTotalTime() {
142 let totaltimeAccumulation = timer.timeAccumulation * 10 + timer.s * 10;
143 this.display.innerHTML = `${Timer.milSecond_to_time(totaltimeAccumulation)}`;
144 }
145 // 计次条目显示
146 displayLapTime() {
147 let li = this.lap.querySelector(‘li‘),
148 spans = li.querySelectorAll(‘span‘),
149 task = spans[0], time = spans[1];
150
151 task.innerHTML = `计次${timer.count}`;
152 time.innerHTML = `${Timer.milSecond_to_time(timer.timeAccumulation * 10)}`;
153 }
154
155 // 设置样式
156 setStyle(newState) {
157 if(newState === ‘startState‘) {
158 let button1 = this.button1;
159 button1.disabled = ‘‘;
160 button1.innerHTML = ‘计次‘;
161 button1.className = ‘active‘;
162
163 let button2 = this.button2;
164 button2.innerHTML = ‘停止‘;
165 button2.className = ‘stop‘;
166 } else if (newState === ‘pauseState‘) {
167 this.button1.innerHTML = ‘复位‘;
168
169 let button2 = this.button2;
170 button2.innerHTML = ‘启动‘;
171 button2.className = ‘start‘;
172 } else if (newState === ‘resetState‘) {
173 let button1 = this.button1;
174 button1.disabled = ‘disabled‘;
175 button1.innerHTML = ‘计次‘;
176 button1.className = ‘‘;
177
178 this.display.innerHTML = ‘00:00.00‘;
179 this.lap.innerHTML = ‘‘;
180 }
181 }
182
183 // 插入一个计次
184 insertLap() {
185 let t = Stopwatch.templateLap();
186 this.lap.insertAdjacentHTML(‘afterbegin‘, t);
187 }
188 static templateLap() {
189 return `
190 <li><span></span><span></span></li>
191 `;
192 }
193
194 // 将函数推入回调队列
195 static addStopwatchListener(handler) {
196 timer.stopwatchHandlers.push(handler);
197 }
198}
199
200// 时间机器(时间插件)
201class Timer {
202 constructor() {
203 // 计时器
204 this.stopwathchTimer = null;
205 this.count = 0; // 计次的次数
206 this.timeAccumulation = 0; // 累积时长
207 this.timeAccumulationContainer = []; // 存放已经结束的计次的容器
208 this.s = 0; // 已经结束的所有计次累积时间
209 this.stopwatchHandlers = []; // 用于startTimer里回调队列
210 }
211
212 reset() {
213 // 重置
214 this.stopwathchTimer = null;
215 this.count = 0;
216 this.timeAccumulation = 0;
217 this.timeAccumulationContainer = [];
218 this.s = 0;
219 }
220
221 startTimer() {
222 this.stopTimer();
223 this.stopwathchTimer = setInterval(() => {
224 this.timeAccumulation++; // 注意时间累积量 _timeAccumulation 是厘秒级别的(由于显示的是两位)
225 this.stopwatchHandlers.forEach(handler => {
226 handler(this.timeAccumulation);
227 })
228 }, 1000 / 100)
229 }
230
231 stopTimer() {
232 clearInterval(this.stopwathchTimer );
233 }
234
235 // 将时间累积量转化成时间
236 static milSecond_to_time(t) {
237 let time,
238 minute = Timer.addZero(Math.floor(t / 60000) % 60), // 分
239 second = Timer.addZero(Math.floor(t / 1000) % 60), // 秒
240 centisecond = Timer.addZero(Math.floor(t / 10) % 100) ; // 厘秒(百分之一秒)
241 time = `${minute}:${second}.${centisecond}`;
242 return time;
243 }
244 // 修饰器;加零
245 static addZero(t) {
246 t = t < 10 ? ‘0‘ + t : t;
247 return t;
248 }
249}
250const timer = new Timer();
251
252// 测试
253const stopwatchObj = new Stopwatch();
254
255stopwatchObj.init();
以上是关于设计模式——状态模式, 实现stopwatch的主要内容,如果未能解决你的问题,请参考以下文章