[状态模式]实现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 - 在工程中试玩状态模式
做了一个项目,项目中一个藏品详情界面针对不同用户,和用户所处于的状态的不同,展示的效果和操作的权限都会不同.想到了状态模式,从来没有用过,赶紧学一下然后用一用.期待兴奋 看了这么多的博客,终于找到一个 ...
随机推荐
- C. Present(二分 + 扫描线)
题目链接:http://codeforces.com/contest/460/problem/C 题意: n盆花,浇k次水, 每次可使花高度 + 1, 每次可浇相邻的w盆,ai 表示 i-th盆花 的 ...
- 使用ASP.NET Core 3.x 构建 RESTful API - 3.2 路由和HTTP方法
ASP.NET Core 3.x 的路由 路由机制会把一个请求的URI映射到一个Controller上面的Action,所以当你发送一个HTTP请求的时候,MVC框架会解析这个请求的URI,并尝试着把 ...
- selenium中延时等待三种方式
selenium中的延时等待方式有三种:强制等待:sleep() 隐示等待:implicitly_wait() 显示等待 WebDriverWait() 1.强制等待:sleep(),time模块 ...
- 程序员用于机器学习数据科学的3个顶级 Python 库
NumPy NumPy(数值 Python 的简称)是其中一个顶级数据科学库,它拥有许多有用的资源,从而帮助数据科学家把 Python 变成一个强大的科学分析和建模工具.NumPy 是在 BSD 许可 ...
- es6 map的用法
let arr =[ {title:'aaaa',read:100,hot:true}, {title:'bbbb',read:50,hot:false}, {title:'ccc',read:100 ...
- 使用 buildx 构建多平台 Docker 镜像
原文链接:使用 buildx 构建多平台 Docker 镜像 在工作和生活中,我们可能经常需要将某个程序跑在不同的 CPU 架构上,比如让某些不可描述的软件运行在树莓派或嵌入式路由器设备上.特别是 D ...
- Linux服务和systemctl详解
定义 A Linux service is an application (or set of applications) that runs in the background waiting to ...
- mybatis源码学习(三)-一级缓存二级缓存
本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点 1.一级缓存配置信息 2.一级缓存源码学习笔记 3.二级缓存配置信息 4.二级缓存源码 5.一级缓存.二级缓存总结 1.一级缓存配 ...
- java中关键字volatile的作用(转载)
转载:http://blog.csdn.net/orzorz/article/details/4319055 用在多线程,同步变量. 线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对 ...
- C程序设计风格
问:如何在源文件中合理分配函数? 答:通常,相关的函数放在同一个文件中.有时候(例如开发库的时候),一个源文件(自然也 就是一个目标文件)放一个函数比较合适.有时候,尤其是对某些程序员,太多的源文件可 ...