设计模式——状态模式, 实现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的主要内容,如果未能解决你的问题,请参考以下文章

stout代码分析之六:Stopwatch

(十三)备忘录模式-代码实现

状态模式

《Android源代码设计模式解析与实战》读书笔记

设计模式 行为型模式 -- 状态模式

Qt学习之秒表的实现(StopWatch) (转)