[状态模式]实现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 - 在工程中试玩状态模式
做了一个项目,项目中一个藏品详情界面针对不同用户,和用户所处于的状态的不同,展示的效果和操作的权限都会不同.想到了状态模式,从来没有用过,赶紧学一下然后用一用.期待兴奋 看了这么多的博客,终于找到一个 ...
随机推荐
- 区块链轻松上手:原理、源码、搭建与应用pdf电子版下载
区块链轻松上手:原理.源码.搭建与应用pdf电子版下载 链接:https://pan.baidu.com/s/1rKF4U9wq612RMIChs0zv8w提取码:hquz <区块链轻松上手:原 ...
- java编程思想第四版第八章习题
第一题 package net.mindview.polymorphism; //基类-自行车 class Cycle{ } //子类-单轮车 class Unicycle extends Cycle ...
- 【java】关于Cannot refer to the non-final local variable list defined in an enclosing scope解决方法
今天学习中遇到了一个问题: Cannot refer to the non-final local variable list defined in an enclosing scope 这里的new ...
- Kotlin Coroutines不复杂, 我来帮你理一理
Coroutines 协程 最近在总结Kotlin的一些东西, 发现协程这块确实不容易说清楚. 之前的那篇就写得不好, 所以决定重写. 反复研究了官网文档和各种教程博客, 本篇内容是最基础也最主要的内 ...
- [ubuntu篇] 使用Hexo建立个人博客,自定义域名https加密,搜索引擎google,baidu,360收录
为了更好的阅读体验,欢迎阅读原文.原文链接在此. Part 1: Using Github Pages and Hexo to manage personal blogs. Series Part 1 ...
- 如何进行Flink项目构建,快速开发Flink应用程序?
项目模板 Flink应用项目可以使用Maven或SBT来构建项目,Flink针对这些构建工具提供了相应项目模板. Maven模板命令如下,我们只需要根据提示输入应用项目的groupId.artifac ...
- Magicodes.IE之导入学生数据教程
基础教程之导入学生数据 说明 本教程主要说明如果使用Magicodes.IE.Excel完成学生数据的Excel导入. 要点 本教程使用Magicodes.IE.Excel来完成Excel数据导入 需 ...
- php+redis实现注册、删除、编辑、分页、登录、关注等功能
本文实例讲述了php+redis实现注册.删除.编辑.分页.登录.关注等功能.分享给大家供大家参考,具体如下: 主要界面 连接redis redis.php <?php //实例化 $red ...
- scrapy的CrawlSpider类
了解CrawlSpider 踏实爬取一般网站的常用spider,其中定义了一些规则(rule)来提供跟进link的方便机制,也许该spider不适合你的目标网站,但是对于大多数情况是可以使用的.因此, ...
- Java常见网络操作(URL类,InetAddress类,URLConnection类)
*****************InetAddress********************** InetAddress:用于标识网络上的硬件资源(如,IP,主机名,域名等). 对于Inet ...