图文无关:TCP 协议状态机
1. 什么是【状态机】?
有限状态机(Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
有限状态机是一种用来进行对象行为建模的工具,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。在计算机科学中,有限状态机被广泛用于建模应用行为、硬件电路系统设计、软件工程,编译器、网络协议、和计算与语言的研究。
状态机的 UML 表示法
基本元素:
入门示例:
复杂一点:
再复杂一点:
状态机设计注意事项:
2. 设计模式——状态模式
2.1. 什么是状态模式?
官方:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
解释:
2.2. 优缺点?
优点:
缺点:
2.3. 架构图?
3. 案例分析——MsgBox
3.1. 接口设计
MsgBox.show(summary); // 只提示摘要信息
MsgBox.show(summary, detail); // 同时提示摘要、详细信息
MsgBox.hide(); // 隐藏信息提示框
MsgBox.fix(); // 固定在屏幕上(屏蔽超时自动隐藏)
功能性要求:
1. 只显示摘要信息时,5s 超时后自动隐藏;
2. 同时显示摘要、详细信息时,10s 超时后自动隐藏;
3. 同时显示摘要、详细信息时,可以控制展开、关闭详细信息面板;
4. 同时显示摘要、详细信息时,展开、关闭详细信息面板时,超时计时器重置;
5. 面板上提示自动关闭倒计时;
3.2. UI交互设计(灵魂版)
3.3. 行为驱动版设计(Vue实现)
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style type="text/css">
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.webj2ee-button {
border: 2px solid orange;
cursor: pointer;
}
.webj2ee-button.active {
background-color: #6495ed;
}
.webj2ee-msgbox {
position: absolute;
top: 50px;
left: 0;
right: 0;
margin: auto;
width: 300px;
border: 2px solid grey;
}
.webj2ee-msgbox-title {
display: flex;
flex-direction: row;
}
.webj2ee-msgbox-title-summary {
border: 2px solid green;
flex-grow: 1;
}
.webj2ee-msgbox-title-btngroup {
border: 2px solid yellow;
}
.webj2ee-msgbox-content-detail {
border: 2px solid purple;
min-height: 100px;
}
</style>
</head>
<body>
<div id="app">
<div v-show="!hidden" class="webj2ee-msgbox">
<div class="webj2ee-msgbox-title">
<div class="webj2ee-msgbox-title-summary">
{{ summary }}
</div>
<div class="webj2ee-msgbox-title-btngroup">
<span>{{ countdown }}</span>
<span class="webj2ee-button"
v-show="!!detail"
v-bind:class="{ active: showdetail }"
v-on:click="toggleShowDetail()">detail</span>
<span class="webj2ee-button"
v-bind:class="{ active: fixed }"
v-on:click="toggleFixed()">fix</span>
<span class="webj2ee-button"
v-on:click="hide()">×</span>
</div>
</div>
<div class="webj2ee-msgbox-content" v-show="!!detail && showdetail">
<div class="webj2ee-msgbox-content-detail">
{{ detail }}
</div>
</div>
</div>
<div>
<button v-on:click="show('Hello World!')">
MsgBox.show("Hello World!")
</button>
<br/>
<button v-on:click="show('Hello World!', 'A message from webj2ee!')">
MsgBox.show("Hello World!", "A message from webj2ee!")
</button>
<br/>
<button v-on:click="fix()">MsgBox.fix()</button>
<br/>
<button v-on:click="hide()">MsgBox.hide()</button>
</div>
</div>
<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
hidden: true,
summary: '',
detail: '',
showdetail: false,
fixed: false,
autoHideTimer: null,
countdown: NaN,
countdownInterval: null,
},
methods: {
show: function (summary, detail = "") {
this.hidden = false;
this.summary = summary;
this.detail = detail;
this.showdetail = !!detail;
this.fixed = false;
clearTimeout(this.autoHideTimer);
this.autoHideTimer = null;
this.countdown = NaN;
if (!this.detail) {
this.countdown = 5;
this.autoHideTimer = setTimeout(() => {
this.hide();
clearInterval(this.countdownInterval);
this.countdownInterval = null;
}, 5 * 1000);
clearInterval(this.countdownInterval);
this.countdownInterval = setInterval(() => {
this.countdown--;
}, 1000)
} else {
this.countdown = 10;
this.autoHideTimer = setTimeout(() => {
this.hide();
clearInterval(this.countdownInterval);
this.countdownInterval = null;
}, 10 * 1000);
clearInterval(this.countdownInterval);
this.countdownInterval = setInterval(() => {
this.countdown--;
}, 1000);
}
},
hide: function () {
this.hidden = true;
clearTimeout(this.autoHideTimer);
this.autoHideTimer = null;
this.countdown = NaN;
},
fix: function () {
this.fixed = true;
clearTimeout(this.autoHideTimer);
this.autoHideTimer = null;
this.countdown = NaN;
clearInterval(this.countdownInterval);
},
toggleFixed: function () {
this.fixed = !this.fixed;
if (this.fixed) {
clearTimeout(this.autoHideTimer);
this.autoHideTimer = null;
this.countdown = NaN;
clearInterval(this.countdownInterval);
} else {
if (!this.detail) {
this.countdown = 5;
this.autoHideTimer = setTimeout(() => {
this.hide();
clearInterval(this.countdownInterval);
this.countdownInterval = null;
}, 5 * 1000);
clearInterval(this.countdownInterval);
this.countdownInterval = setInterval(() => {
this.countdown--;
}, 1000);
} else {
this.countdown = 10;
this.autoHideTimer = setTimeout(() => {
this.hide();
clearInterval(this.countdownInterval);
this.countdownInterval = null;
}, 10 * 1000);
clearInterval(this.countdownInterval);
this.countdownInterval = setInterval(() => {
this.countdown--;
}, 1000);
}
}
},
toggleShowDetail: function () {
this.showdetail = !this.showdetail;
clearTimeout(this.autoHideTimer);
this.autoHideTimer = null;
this.countdown = 10;
this.autoHideTimer = setTimeout(() => {
this.hide();
clearInterval(this.countdownInterval);
this.countdownInterval = null;
}, 10 * 1000);
clearInterval(this.countdownInterval);
this.countdownInterval = setInterval(() => {
this.countdown--;
}, 1000);
}
}
})
</script>
</body>
</html>
行为驱动板实现的编码特点:
1.4. 状态机版设计(Vue实现)
a. 首先绘制MsgBox的状态转换图:
b. 再根据状态转换图编码:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style type="text/css">
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
.webj2ee-button {
border: 2px solid orange;
cursor: pointer;
}
.webj2ee-button.active {
background-color: #6495ed;
}
.webj2ee-msgbox {
position: absolute;
top: 50px;
left: 0;
right: 0;
margin: auto;
width: 300px;
border: 2px solid grey;
}
.webj2ee-msgbox-title {
display: flex;
flex-direction: row;
}
.webj2ee-msgbox-title-summary {
border: 2px solid green;
flex-grow: 1;
}
.webj2ee-msgbox-title-btngroup {
border: 2px solid yellow;
}
.webj2ee-msgbox-content-detail {
border: 2px solid purple;
min-height: 100px;
}
.webj2ee-msgbox-content-datetimer{
text-align: center;
color: purple;
}
</style>
</head>
<body>
<div id="app">
<div v-show="!hidden" class="webj2ee-msgbox">
<div class="webj2ee-msgbox-title">
<div class="webj2ee-msgbox-title-summary">
{{ summary }}
</div>
<div class="webj2ee-msgbox-title-btngroup">
<span>{{ autoHiddenCountdown }}</span>
<span class="webj2ee-button"
v-show="!!detail"
v-bind:class="{ active: detailExpaneded }"
v-on:click="toggleDetailExpaneded()">detail</span>
<span class="webj2ee-button"
v-bind:class="{ active: fixed }"
v-on:click="toggleFixed()">fix</span>
<span class="webj2ee-button"
v-on:click="hide()">×</span>
</div>
</div>
<div class="webj2ee-msgbox-content" v-show="!!detail && detailExpaneded">
<div class="webj2ee-msgbox-content-detail">
{{ detail }}
</div>
<div class="webj2ee-msgbox-content-datetimer">
{{ currentDateFormatted }}
</div>
</div>
</div>
<div>
<button v-on:click="show('Hello World!')">
MsgBox.show("Hello World!")
</button>
<br/>
<button v-on:click="show('Hello World!', 'A message from webj2ee!')">
MsgBox.show("Hello World!", "A message from webj2ee!")
</button>
<br/>
<button v-on:click="fix()">MsgBox.fix()</button>
<br/>
<button v-on:click="hide()">MsgBox.hide()</button>
</div>
</div>
<script type="text/javascript">
const MSGBOX_STATE_INIT = 0;
const MSGBOX_STATE_SUMMARY_UNFIXED = 1;
const MSGBOX_STATE_SUMMARY_FIXED = 2;
const MSGBOX_STATE_DETAIL_EXPANDED_UNFIXED = 3;
const MSGBOX_STATE_DETAIL_EXPANDED_FIXED = 4;
const MSGBOX_STATE_DETAIL_COLLAPSED_UNFIXED = 5;
const MSGBOX_STATE_DETAIL_COLLAPSED_FIXED = 6;
const MSGBOX_STATE_HIDDEN = 7;
const MSGBOX_SUMMARY_AUTO_HIDDEN_TIMEOUT_SEC = 5;
const MSGBOX_SUMMARY_DETAIL_AUTO_HIDDEN_TIMEOUT_SEC = 10;
var app = new Vue({
el: '#app',
data: {
state: MSGBOX_STATE_INIT,
hidden: true,
summary: '',
detail: '',
detailExpaneded: false,
fixed: false,
autoHiddenTimer: null,
autoHiddenCountdown: NaN,
autoHiddenCountdownRefreshInterval: null,
currentDate: null,
currentDateRefreshInterval: null,
},
computed:{
currentDateFormatted: function(){
const now = this.currentDate;
if(!now){
return "";
}else{
return `${now.getFullYear()}-${now.getMonth()+1}-${now.getDay()} ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`
}
}
},
mounted: function () {
this.setState(MSGBOX_STATE_INIT);
},
methods: {
/**
* 辅助函数
*/
_stopAutoHiddenTimer: function(){
// stop auto hidden timer
clearTimeout(this.autoHiddenTimer);
this.autoHiddenTimer = null;
// stop auto hidden countdown refresh
this.autoHiddenCountdown = NaN;
clearInterval(this.autoHiddenCountdownRefreshInterval);
this.autoHiddenCountdownRefreshInterval = null;
},
_startAutoHiddenTimer: function(autoHiddenTimeoutSec){
// 开启自动关闭超时控制
clearTimeout(this.autoHiddenTimer);
this.autoHiddenTimer = setTimeout(() => {
this.setState(MSGBOX_STATE_HIDDEN);
}, autoHiddenTimeoutSec * 1000);
// 开启自动关闭超时倒计时
this.autoHiddenCountdown = autoHiddenTimeoutSec;
clearInterval(this.autoHiddenCountdownRefreshInterval);
this.autoHiddenCountdownRefreshInterval = setInterval(() => {
this.autoHiddenCountdown--;
}, 1000);
},
_stopCurrentDateRefresh: function(){
this.currentDate = null;
clearInterval(this.currentDateRefreshInterval);
this.currentDateRefreshInterval = null;
},
_startCurrentDateRefresh: function(){
// 开启用当前时间展示
this.currentDate = new Date();
clearInterval(this.currentDateRefreshInterval);
this.currentDateRefreshInterval = setInterval(() => {
this.currentDate = new Date();
}, 1000);
},
/**
* 状态 -> 行为控制
*/
setState: function(state){
this.state = state;
if(state === MSGBOX_STATE_INIT ||
state === MSGBOX_STATE_HIDDEN){ // 初始、隐藏状态
this.hidden = true;
this.summary = "";
this.detail = "";
this.detailExpaneded = false;
this.fixed = false;
this._stopAutoHiddenTimer();
this._stopCurrentDateRefresh();
}else if(state === MSGBOX_STATE_SUMMARY_UNFIXED){ // 摘要-不固定
this.hidden = false;
this.detailExpaneded = false;
this.fixed = false;
this._startAutoHiddenTimer(MSGBOX_SUMMARY_AUTO_HIDDEN_TIMEOUT_SEC);
this._stopCurrentDateRefresh();
}else if(state === MSGBOX_STATE_SUMMARY_FIXED){ // 摘要-固定
this.fixed = true;
this._stopAutoHiddenTimer();
}else if(state === MSGBOX_STATE_DETAIL_EXPANDED_UNFIXED
|| state === MSGBOX_STATE_DETAIL_COLLAPSED_UNFIXED){
this.hidden = false;
if(state === MSGBOX_STATE_DETAIL_EXPANDED_UNFIXED){
this.detailExpaneded = true;
}else if(state === MSGBOX_STATE_DETAIL_COLLAPSED_UNFIXED){
this.detailExpaneded = false;
}
this.fixed = false;
this._startAutoHiddenTimer(MSGBOX_SUMMARY_DETAIL_AUTO_HIDDEN_TIMEOUT_SEC);
this._startCurrentDateRefresh();
}else if(state === MSGBOX_STATE_DETAIL_EXPANDED_FIXED
|| state === MSGBOX_STATE_DETAIL_COLLAPSED_FIXED){ // 摘要-固定
this.fixed = true;
this._stopAutoHiddenTimer();
}
},
show: function (summary="", detail = "") {
this.summary = summary;
this.detail = detail;
if(!detail){
this.setState(MSGBOX_STATE_SUMMARY_UNFIXED);
}else{
this.setState(MSGBOX_STATE_DETAIL_EXPANDED_UNFIXED);
}
},
hide: function () {
this.setState(MSGBOX_STATE_HIDDEN);
},
fix: function () {
this.toggleFixed(true);
},
toggleFixed: function (forceFixed) {
// 摘要状态
if(forceFixed || this.state === MSGBOX_STATE_SUMMARY_UNFIXED){
this.setState(MSGBOX_STATE_SUMMARY_FIXED);
}else if(this.state === MSGBOX_STATE_SUMMARY_FIXED){
this.setState(MSGBOX_STATE_SUMMARY_UNFIXED);
}
// 详细状态
// --展开状态
if(forceFixed || this.state === MSGBOX_STATE_DETAIL_EXPANDED_UNFIXED){
this.setState(MSGBOX_STATE_DETAIL_EXPANDED_FIXED);
}else if(this.state === MSGBOX_STATE_DETAIL_EXPANDED_FIXED){
this.setState(MSGBOX_STATE_DETAIL_EXPANDED_UNFIXED);
}
// --收缩状态
if(forceFixed || this.state === MSGBOX_STATE_DETAIL_COLLAPSED_UNFIXED){
this.setState(MSGBOX_STATE_DETAIL_COLLAPSED_FIXED);
}else if(this.state === MSGBOX_STATE_DETAIL_COLLAPSED_FIXED){
this.setState(MSGBOX_STATE_DETAIL_COLLAPSED_UNFIXED);
}
},
toggleDetailExpaneded: function () {
if(this.state === MSGBOX_STATE_DETAIL_EXPANDED_UNFIXED){
this.setState(MSGBOX_STATE_DETAIL_COLLAPSED_UNFIXED);
}else if(this.state === MSGBOX_STATE_DETAIL_COLLAPSED_UNFIXED){
this.setState(MSGBOX_STATE_DETAIL_EXPANDED_UNFIXED);
}
}
}
})
</script>
</body>
</html>
状态驱动板实现的特点:
参考:
《大话设计模式》 《设计模式之禅 第2版》 《研磨设计模式》 《敏捷软件开发 原则、模式与实践》 《面向对象分析与设计》 《UML 基础、案例与应用》 《设计模式 可复用面向对象软件的基础》