[状态模式]实现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 - 在工程中试玩状态模式
做了一个项目,项目中一个藏品详情界面针对不同用户,和用户所处于的状态的不同,展示的效果和操作的权限都会不同.想到了状态模式,从来没有用过,赶紧学一下然后用一用.期待兴奋 看了这么多的博客,终于找到一个 ...
随机推荐
- java编程思想第四版第十章习题
第一题 package net.mindview.innerclasses; public class Outer { class Inner { Inner(){ System.out.printl ...
- nyoj 76-超级台阶 (递推)
76-超级台阶 内存限制:64MB 时间限制:1000ms 特判: No 通过数:8 提交数:12 难度:3 题目描述: 有一楼梯共m级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第m级,共 ...
- Base系列编码浅析【base16 base32 base64 base85 base36 base 58 base91 base 92 base62】
Base系列编码浅析 [base16 base32 base64 base85 base36 base 58 base91 base 92 base62] base编码 ...
- linux bash编程之算数运算和测试类型(第二篇)
写在最前边:在bash中数据类型有两种,分别是数值型和字符型.其中字符型是默认的. 1.算数运算 · 运算符 · 语法 1.1.运算符:+.-.*./.%.** 注意:有些时候 *(乘号)需要转义 1 ...
- C#学习笔记05--枚举/结构体
一.枚举 当变量的取值范围是固定的几个时, 就可以使用枚举类型, 这样会更加简洁方便 1.1.定义: 访问修饰符 enum 枚举类型名 { 成员1, 成员2, 成员3, ... } publi ...
- mui开发:苹果手机自动全屏解决方案
前一段时间,使用mui写app时,出现了苹果手机播放视频时,自动全屏的情况,并且无法点击控件,只能等到播放完毕后点击控件. 那么怎么解决这个问题呢,弟弟们请看我的下面. 1.在页面的video标签中, ...
- FaceBook的秘钥散列获取
随笔记录 先下载OpenSSL工具 在C盘创建一个openssl,将下好的OpenSSL工具解压到这里 将你的 .keystore文件复制到JAVA JDK 文件夹的bin目录里面(C:\Progra ...
- LNMP架构源码搭建(centos7)
第一步:安装nginx 1.上传或下载nginx,并解压 yum -y install lrzsz rz .tar.gz 2.搭建nginx安装环境 yum -y install gcc-c++ zl ...
- spring源码学习五 - xml格式配置,如何解析
spring在注入bean的时候,可以通过bean.xml来配置,在xml文件中配置bean的属性,然后spring在refresh的时候,会去解析xml配置文件,这篇笔记,主要来记录.xml配置文件 ...
- 小白的springboot之路(五)、集成druid
0-前言 Druid阿里巴巴开源的一个java数据库连接池,是Java语言中最好的数据库连接池,Druid能够提供强大的监控和扩展功能:集成它能够方便我们对数据库连接进行监控和分析,下面我们来集成它: ...