[状态模式]实现stopwatch
1.模拟传统面向对象语言的状态模式实现
// Stopwatch类 状态机
class Stopwatch {
constructor() {
this.button1 = null;
this.button2 = null;
this.resetState = new ResetState(this); // 设置初始状态为 reset 重置
this.startState = new StartState(this);
this.pauseState = new PauseState(this);
this.currState = this.resetState; // 设置当前状态
}
/****** 创建DOM节点, 绑定事件 ******/
init() {
this.dom = document.createElement('div');
this.dom.setAttribute('id', 'stopwatch');
this.dom.innerHTML = `
<div class="header">stopwatch</div>
<div class="main">
<!-- 时间显示部分 -->
<div class="display" >00:00.00</div>
<!-- 控制部分 -->
<div class="ctrl">
<button type="button" id='btnOne' disabled='disabled'>计次</button> <!--class="active"-->
<button type="button" id='btnTwo'>启动</button> <!--class="stop"-->
</div>
<!-- 显示计次(时间间隔) -->
<ol class="lap">
</ol>
</div>`;
document.body.appendChild(this.dom);
this.button1 = this.dom.querySelector('#btnOne');
this.button2 = this.dom.querySelector('#btnTwo');
this.display = this.dom.querySelector('.display'); // 总时间显示
this.lap = this.dom.querySelector('.lap'); // 计次显示
this.bindEvent();
Stopwatch.addStopwatchListener(t => {
this.displayTotalTime(t);
});
Stopwatch.addStopwatchListener(t => {
this.displayLapTime(t);
})
}
// 将请求委托给当前状态类来执行
bindEvent() {
this.button1.addEventListener('click', () => {
this.currState.clickHandler1();
}, false);
this.button2.addEventListener('click', () => {
this.currState.clickHandler2();
}, false);
}
/****** 状态对应的逻辑行为 ******/
// 开始
start() {
if(timer.count === 0) {
timer.count = 1;
this.insertLap();
}
// 行为
timer.startTimer();
// 状态
this.setState(this.startState);
// 样式
this.setStyle();
}
// 暂停
pause() {
// 行为
timer.stopTimer();
// 状态
this.setState(this.pauseState);
// 样式
this.setStyle();
}
// 计次
lapf() {
// 行为
this.insertLap();
timer.timeAccumulationContainer.push(timer.timeAccumulation);
timer.s += timer.timeAccumulationContainer[timer.count - 1];
timer.timeAccumulation = 0;
timer.count++;
}
// 重置
reset() {
// 行为
timer.reset();
// 状态
this.setState(this.resetState);
// 样式
this.setStyle();
}
/****** 辅助方法 ******/
// 总时间显示(从启动到当前时刻的累积时间)
displayTotalTime() {
let totaltimeAccumulation = timer.timeAccumulation * 10 + timer.s * 10;
this.display.innerHTML = `${Timer.milSecond_to_time(totaltimeAccumulation)}`;
}
// 计次条目显示
displayLapTime() {
let li = this.lap.querySelector('li'),
spans = li.querySelectorAll('span'),
task = spans[0], time = spans[1];
task.innerHTML = `计次${timer.count}`;
time.innerHTML = `${Timer.milSecond_to_time(timer.timeAccumulation * 10)}`;
}
// 设置状态
setState(newState) {
this.currState = newState;
}
// 设置样式
setStyle() {
if(this.currState instanceof StartState) {
let button1 = this.button1;
button1.disabled = '';
button1.innerHTML = '计次';
button1.className = 'active';
let button2 = this.button2;
button2.innerHTML = '停止';
button2.className = 'stop';
} else if (this.currState instanceof PauseState) {
this.button1.innerHTML = '复位';
let button2 = this.button2;
button2.innerHTML = '启动';
button2.className = 'start';
} else if (this.currState instanceof ResetState) {
let button1 = this.button1;
button1.disabled = 'disabled';
button1.innerHTML = '计次';
button1.className = '';
this.display.innerHTML = '00:00.00';
this.lap.innerHTML = '';
}
}
// 插入一个计次
insertLap() {
let t = Stopwatch.templateLap();
this.lap.insertAdjacentHTML('afterbegin', t);
}
static templateLap() {
return `
<li><span></span><span></span></li>
`;
}
// 将函数推入回调队列
static addStopwatchListener(handler) {
timer.stopwatchHandlers.push(handler);
}
}
// 编写各个状态类的实现
// 模拟State抽象类; 在Java中,所有的状态类必须继承自一个State抽象父类
class State{
constructor() {}
static clickHandler1() {
throw new Error('父类的clickHandler1方法必须被重写');
}
static clickHandler2() {
throw new Error('父类的clickHandler2方法必须被重写');
}
}
// 状态类
class ResetState {
constructor(stopwatchObj) {
this.stopwatchObj = stopwatchObj;
}
static clickHandler1() {
console.log('初始状态下点击计次无效');
}
clickHandler2() {
console.log('初始状态下点击启动, 切换为启动状态');
//
this.stopwatchObj.start();
}
}
ResetState.prototype = new State(); // 继承抽象父类, 用于检测
class StartState {
constructor(stopwatchObj) {
this.stopwatchObj = stopwatchObj;
}
clickHandler1() {
console.log('启动状态下点击计次');
//
this.stopwatchObj.lapf();
}
clickHandler2() {
console.log('启动状态下点击暂停, 切换为暂停状态');
//
this.stopwatchObj.pause();
}
}
StartState.prototype = new State(); // 继承抽象父类, 用于检测
class PauseState {
constructor(stopwatchObj) {
this.stopwatchObj = stopwatchObj;
}
clickHandler1() {
console.log('暂停状态下点击复位, 切换为初始状态');
//
this.stopwatchObj.reset();
}
clickHandler2() {
console.log('暂停状态下点击启动, 切换为启动状态');
//
this.stopwatchObj.start();
}
}
PauseState.prototype = new State(); // 继承抽象父类, 用于检测
// 时间机器(时间插件)
class Timer {
constructor() {
// 计时器
this.stopwathchTimer = null;
this.count = 0; // 计次的次数
this.timeAccumulation = 0; // 累积时长
this.timeAccumulationContainer = []; // 存放已经结束的计次的容器
this.s = 0; // 已经结束的所有计次累积时间
this.stopwatchHandlers = []; // 用于startTimer里回调队列
}
reset() {
// 重置
this.stopwathchTimer = null;
this.count = 0;
this.timeAccumulation = 0;
this.timeAccumulationContainer = [];
this.s = 0;
}
startTimer() {
this.stopTimer();
this.stopwathchTimer = setInterval(() => {
this.timeAccumulation++; // 注意时间累积量 _timeAccumulation 是厘秒级别的(由于显示的是两位)
this.stopwatchHandlers.forEach(handler => {
handler(this.timeAccumulation);
})
}, 1000 / 100)
}
stopTimer() {
clearInterval(this.stopwathchTimer );
}
// 将时间累积量转化成时间
static milSecond_to_time(t) {
let time,
minute = Timer.addZero(Math.floor(t / 60000) % 60), // 分
second = Timer.addZero(Math.floor(t / 1000) % 60), // 秒
centisecond = Timer.addZero(Math.floor(t / 10) % 100) ; // 厘秒(百分之一秒)
time = `${minute}:${second}.${centisecond}`;
return time;
}
// 修饰器;加零
static addZero(t) {
t = t < 10 ? '0' + t : t;
return t;
}
}
const timer = new Timer();
// 测试
const stopwatchObj = new Stopwatch();
stopwatchObj.init();
2.JavaScript 版本的状态机
// Stopwatch类 状态机
class Stopwatch {
constructor() {
this.button1 = null;
this.button2 = null;
// JavaScript 版本的状态机
this.FSM = {
/****** 状态对应的逻辑行为 ******/
// btn1无效 / btn2复位 >> 开始
reset: {
clickHandler1() {
// 复位状态下计次按钮无效
},
clickHandler2() {
console.log('初始状态下点击启动, 切换为启动状态');
if(timer.count === 0) {
timer.count = 1;
this.insertLap();
}
// 行为
timer.startTimer();
// 切换状态
this.currState = this.FSM.start;
// 样式
this.setStyle('startState');
}
},
// bnt1计次 / btn2开始 >> 暂停
start: {
clickHandler1() {
console.log('启动状态下点击计次, 不切换状态');
// 行为
this.insertLap();
timer.timeAccumulationContainer.push(timer.timeAccumulation);
timer.s += timer.timeAccumulationContainer[timer.count - 1];
timer.timeAccumulation = 0;
timer.count++;
// 状态不改变
// 样式不改变
},
clickHandler2() {
console.log('启动状态下点击暂停, 切换为暂停状态');
// 行为
timer.stopTimer();
// 切换状态
this.currState = this.FSM.pause;
// 样式
this.setStyle('pauseState');
}
},
// btn1暂停 >> 重置 / btn2暂停 >> 开始
pause: {
clickHandler1() {
console.log('暂停状态下点击复位, 切换为初始状态');
// 行为
timer.reset();
// 切换状态
this.currState = this.FSM.reset;
// 样式
this.setStyle('resetState');
},
clickHandler2() {
console.log('暂停状态下点击启动, 切换为启动状态');
// 行为
timer.startTimer();
// 状态
this.currState = this.FSM.start;
// 样式
this.setStyle('startState');
}
}
};
this.currState = this.FSM.reset; // 设置当前状态
}
/****** 创建DOM节点, 绑定事件 ******/
init() {
this.dom = document.createElement('div');
this.dom.setAttribute('id', 'stopwatch');
this.dom.innerHTML = `
<div class="header">stopwatch</div>
<div class="main">
<!-- 时间显示部分 -->
<div class="display" >00:00.00</div>
<!-- 控制部分 -->
<div class="ctrl">
<button type="button" id='btnOne' disabled='disabled'>计次</button> <!--class="active"-->
<button type="button" id='btnTwo'>启动</button> <!--class="stop"-->
</div>
<!-- 显示计次(时间间隔) -->
<ol class="lap">
</ol>
</div>`;
document.body.appendChild(this.dom);
this.button1 = this.dom.querySelector('#btnOne');
this.button2 = this.dom.querySelector('#btnTwo');
this.display = this.dom.querySelector('.display'); // 总时间显示
this.lap = this.dom.querySelector('.lap'); // 计次显示
this.bindEvent();
Stopwatch.addStopwatchListener(t => {
this.displayTotalTime(t);
});
Stopwatch.addStopwatchListener(t => {
this.displayLapTime(t);
})
}
// 通过 call 方法直接把请求委托给某个字面量对象(FSM)来执行
bindEvent() {
this.button1.addEventListener('click', () => {
this.currState.clickHandler1.call(this); // 把请求委托给FSM 状态机
}, false);
this.button2.addEventListener('click', () => {
this.currState.clickHandler2.call(this);
}, false);
}
/****** 辅助方法 ******/
// 总时间显示(从启动到当前时刻的累积时间)
displayTotalTime() {
let totaltimeAccumulation = timer.timeAccumulation * 10 + timer.s * 10;
this.display.innerHTML = `${Timer.milSecond_to_time(totaltimeAccumulation)}`;
}
// 计次条目显示
displayLapTime() {
let li = this.lap.querySelector('li'),
spans = li.querySelectorAll('span'),
task = spans[0], time = spans[1];
task.innerHTML = `计次${timer.count}`;
time.innerHTML = `${Timer.milSecond_to_time(timer.timeAccumulation * 10)}`;
}
// 设置样式
setStyle(newState) {
if(newState === 'startState') {
let button1 = this.button1;
button1.disabled = '';
button1.innerHTML = '计次';
button1.className = 'active';
let button2 = this.button2;
button2.innerHTML = '停止';
button2.className = 'stop';
} else if (newState === 'pauseState') {
this.button1.innerHTML = '复位';
let button2 = this.button2;
button2.innerHTML = '启动';
button2.className = 'start';
} else if (newState === 'resetState') {
let button1 = this.button1;
button1.disabled = 'disabled';
button1.innerHTML = '计次';
button1.className = '';
this.display.innerHTML = '00:00.00';
this.lap.innerHTML = '';
}
}
// 插入一个计次
insertLap() {
let t = Stopwatch.templateLap();
this.lap.insertAdjacentHTML('afterbegin', t);
}
static templateLap() {
return `
<li><span></span><span></span></li>
`;
}
// 将函数推入回调队列
static addStopwatchListener(handler) {
timer.stopwatchHandlers.push(handler);
}
}
// 时间机器(时间插件)
class Timer {
constructor() {
// 计时器
this.stopwathchTimer = null;
this.count = 0; // 计次的次数
this.timeAccumulation = 0; // 累积时长
this.timeAccumulationContainer = []; // 存放已经结束的计次的容器
this.s = 0; // 已经结束的所有计次累积时间
this.stopwatchHandlers = []; // 用于startTimer里回调队列
}
reset() {
// 重置
this.stopwathchTimer = null;
this.count = 0;
this.timeAccumulation = 0;
this.timeAccumulationContainer = [];
this.s = 0;
}
startTimer() {
this.stopTimer();
this.stopwathchTimer = setInterval(() => {
this.timeAccumulation++; // 注意时间累积量 _timeAccumulation 是厘秒级别的(由于显示的是两位)
this.stopwatchHandlers.forEach(handler => {
handler(this.timeAccumulation);
})
}, 1000 / 100)
}
stopTimer() {
clearInterval(this.stopwathchTimer );
}
// 将时间累积量转化成时间
static milSecond_to_time(t) {
let time,
minute = Timer.addZero(Math.floor(t / 60000) % 60), // 分
second = Timer.addZero(Math.floor(t / 1000) % 60), // 秒
centisecond = Timer.addZero(Math.floor(t / 10) % 100) ; // 厘秒(百分之一秒)
time = `${minute}:${second}.${centisecond}`;
return time;
}
// 修饰器;加零
static addZero(t) {
t = t < 10 ? '0' + t : t;
return t;
}
}
const timer = new Timer();
// 测试
const stopwatchObj = new Stopwatch();
stopwatchObj.init();
[状态模式]实现stopwatch的更多相关文章
- StatePattern(状态模式)
/** * 状态模式 * @author TMAC-J * 状态模式和策略模式很像,其实仔细研究发现完全不一样 * 策略模式各策略之间没有任何关系,独立的 * 状态模式各状态之间接口方法都是一样的 * ...
- 设计模式(十二):通过ATM取款机来认识“状态模式”(State Pattern)
说到状态模式,如果你看过之前发布的重构系列的文章中的<代码重构(六):代码重构完整案例>这篇博客的话,那么你应该对“状态模式”并不陌生,因为我们之前使用到了状态模式进行重构.上一篇博客我们 ...
- php实现设计模式之 状态模式
<?php /*状态模式:允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类.(行为模式) * * 在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做 ...
- Java 策略模式和状态模式
本文是转载的,转载地址:大白话解释Strategy模式和State模式的区别 先上图: 本质上讲,策略模式和状态模式做得是同一件事:去耦合.怎么去耦合?就是把干什么(语境类)和怎么干(策略接口)分开, ...
- javascript - 状态模式 - 简化分支判断流程
状态模式笔记 当一个对象的内部状态发生改变时,会导致行为的改变,这像是改变了对象 状态模式既是解决程序中臃肿的分支判断语句问题,将每个分支转化为一种状态独立出来,方便每种状态的管理又不至于每次 ...
- C#设计模式系列:状态模式(State)
1.状态模式简介 1.1>.定义 状态模式的核心思想是允许一个对象在它的内部状态改变时改变它的行为,即不同的状态对应不同的行为. 状态模式的针对性很强,当有状态变化的时候可以选择状态模式. 1. ...
- 十一个行为模式之状态模式(State Pattern)
定义: 当一个对象有多个状态,并且在每个状态下有不同的行为,可以使用状态模式来在其内部改变状态时改变其行为,而客户端不会察觉状态的改变,仍使用同样的方法或接口与对象进行交互. 结构图: Context ...
- java设计模式之状态模式
状态模式 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类. 状态模式UML图 上下文环境(Context):它定义了客户程序需要的接口并维护一个具体状态角色的实例,将与状态相关 ...
- iOS - 在工程中试玩状态模式
做了一个项目,项目中一个藏品详情界面针对不同用户,和用户所处于的状态的不同,展示的效果和操作的权限都会不同.想到了状态模式,从来没有用过,赶紧学一下然后用一用.期待兴奋 看了这么多的博客,终于找到一个 ...
随机推荐
- windows下安装Apache、php、mysql集成环境
一.准备工作 本次安装的版本分别为:apache2.4 .php5.6 . mysql5.7 下载地址为:http://pan.baidu.com/s/1boQNIOn 密码:zarx 二.安装步骤 ...
- Redux中间件Redux-thunk的配置
当做固定写法吧 截图里少一个括号,已代码为主 import {createStore,applyMiddleware,compose} from 'redux' import thunk from ' ...
- 优秀的github项目学习
优秀的github项目学习 后期会陆续添加遇到的优秀项目 https://github.com/chaijunkun
- 记一个vue-resource请求的低级错误
对于初学的小菜鸡,经常会犯一些低级错误. 现在记录一下我在使用vue-resource发送post请求时的一个低级错误: window.BaseURL = '127.0.0.1:8888'; 8888 ...
- PHP中的服务容器与依赖注入的思想
依赖注入 当A类需要依赖于B类,也就是说需要在A类中实例化B类的对象来使用时候,如果B类中的功能发生改变,也会导致A类中使用B类的地方也要跟着修改,导致A类与B类高耦合.这个时候解决方式是,A类应该去 ...
- Java中的继承、封装、多态的理解
Java中的继承.封装.多态 继承的理解: 1.继承是面向对象的三大特征之一,也是实现代码复用的重要手段.Java的继承具有单继承的特点,每个子类只有一个直接父类. 2.Java的继承通过extend ...
- myBaits持久性框架
动态 SQL 博客交流群:1018996617 动态 SQL MyBatis 的强大特性之一便是它的动态 SQL.如果你有使用 JDBC 或其他类似框架的经验,你就能体会到根据不同条件拼接 SQL ...
- 根据json数据中某一个属性 处理数组重组的方法 (二种)
需求:根据role 的不同分组 渲染页面 进行后期操作 后台返回数据: 因为后台返回的json数据不是我们想要的 所以就得自己来了~ 要啥样整啥样 js: 第一种处理方法 使用方法: 1: th ...
- python3 之 字符串编码小结(Unicode、utf-8、gbk、gb2312等)
python3 解释器默认编码为Unicode,由str类型进行表示.二进制数据使用byte类型表示. 字符串通过编码转换成字节串,字节码通过解码成为字符串. encode:str-->byte ...
- Maven入门【小白千万别点进】
曾经有个女孩问我为什么要学Maven,我吧唧嘴就怼:Maven项目没有jar包它不香嘛,照样运行它不香嘛?如果让我一句话形容Maven,我会这样形容:"妈妈再也不用担心小明拿U盘去小红电脑里 ...