[状态模式]实现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 - 在工程中试玩状态模式
做了一个项目,项目中一个藏品详情界面针对不同用户,和用户所处于的状态的不同,展示的效果和操作的权限都会不同.想到了状态模式,从来没有用过,赶紧学一下然后用一用.期待兴奋 看了这么多的博客,终于找到一个 ...
随机推荐
- git push后出现错误 ![rejected] master -> master(non-fast-forward) error:failed to push some refs to 'XXX'
本地创建了一个project并在GitHub上创建了一个仓库,想要将本地的仓库链接到远程仓库我用的是如下方法:git init //初始化本地仓库git remote add origin XX ...
- 用c语言打印一个三角形
#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<string.h>#include<stdlib.h&g ...
- Look into Bitmap images
What's a Bitmap image? I'm not going to explain the differences between raster and vector images, no ...
- 用正则表达式获取URL中的查询参数
总结获取url中查询参数的两种方式 通过正则表达式获取单个参数 url中的所有查询参数可以通过 window.location.search 字段获取,以字符串的形式返回.并有固定的格式 ?param ...
- [LC]88题 Merge Sorted Array (合并两个有序数组 )
①英文题目 Given two sorted integer arrays nums1 and nums2, merge nums2 into nums1 as one sorted array. N ...
- 【Elasticsearch 7 探索之路】(三)倒排索引
上一篇,我们介绍了 ES 文档的基本 CURE 和批量操作.我们都知道倒排索引是搜索引擎非常重要的一种数据结构,什么是倒排索引,倒排索引的原理是什么. 1 索引过程 在讲解倒排索引前,我们先了解索引创 ...
- EFCore批量操作,你真的清楚吗
背景 EntityFramework Core有许多新的特性,其中一个重要特性便是批量操作. 批量操作意味着不需要为每次Insert/Update/Delete操作发送单独的命令,而是在一次SQL请求 ...
- openresty(nginx)中使用lua脚本获取请求IP地址的代码
人狠话不多,直接上代码:------------------------------------------------------------------------------------- lo ...
- windows系统与SQL SERVER 2008数据库服务性能监控分析简要
软件系统性能测试体系流程介绍之windows系统与SQL SERVER 2008数据库服务性能监控分析简要 目前大部分测试人员对操作系统资源.中间件.数据库等性能监控分析都是各自分析各自的监控指标方式 ...
- 新闻实时分析系统 基于IDEA环境下的Spark2.X程序开发
1.Windows开发环境配置与安装 下载IDEA并安装,可以百度一下免费文档. 2.IDEA Maven工程创建与配置 1)配置maven 2)新建Project项目 3)选择maven骨架 4)创 ...