
前言
这是关于设计模式的系列文章,在每篇文章中将对常见设计模式进行讲解,因为针对前端方向,而且前端常用语言
JavaScript
本身是弱类型,面向对象(模拟面向对象)编程的实现相较于其他强类型语言实现更为繁琐,所以代码主要以JavaScript
表现。
系列文章链接:
状态模式简介
有些情况下一个对象的行为取决于一个或者多个动态变化的属性,这样的属性叫做状态,这个对象叫做有状态的对象,这种情况下通常有很多的判断来处理状态不同时代码的执行逻辑,执行逻辑可能会非常复杂,让代码变得难以维护,“状态模式” 就是将这些逻辑委托给外面的对象或类来单独维护,来减少状态对象的逻辑,增强代码的维护性。

状态模式的实现
下面是一个类,功能为根据电池不同状态打印当前不同的颜色,下面是正常的实现方式。
class Battery {
constructor() {
this.amount = 'high'; // 电量高
}
show() {
if (this.amount === 'high') {
console.log('显示绿色');
this.amount = 'middle'; // 电量中等
} else if (this.amount === 'middle') {
console.log('显示黄色');
this.amount = 'low'; // 电量低
} else if (this.amount === 'low') {
console.log('显示红色');
}
}
}
const battery = new Battery();
battery.show(); // 显示绿色
battery.show(); // 显示黄色
battery.show(); // 显示红色
上面的代码虽然实现了我们想要的功能,但是代码中 show
方法违反了开放封闭原则和单一职责原则,状态切换逻辑不明显,判断条件太多,维护性和扩展性差,下面使用状态模式进行优化。
class Battery {
constructor() {
this.amount = 'high'; // 电量高
this.state = new SuccessState(); // 绿色状态
}
show() {
// 把显示逻辑委托给状态对象
this.state.show();
if (this.amount === 'high') {
this.amount = 'middle'; // 电量中等
this.state = new WarningState(); // 黄色状态
} else if (this.amount === 'middle') {
this.amount = 'low'; // 电量低
this.state = new ErrorState(); // 红色状态
}
}
}
class SuccessState {
show() {
console.log('显示绿色');
}
}
class WarningState {
show() {
console.log('显示黄色');
}
}
class ErrorState {
show() {
console.log('显示红色');
}
}
const battery = new Battery();
battery.show(); // 显示绿色
battery.show(); // 显示黄色
battery.show(); // 显示红色
经过 “状态模式” 的优化,我们将状态拆分成三个类,无论关于状态操作的多复杂的逻辑都在拆分出的类中实现,而不再需要在状态对象 Battery
中。
状态模式的应用
点赞
点赞是我们在项目开发中经常见到的功能,点赞后也可以取消点赞,这就出现了按钮关于点赞状态的切换和按钮文案的切换,下面是使用 “状态模式” 来实现的点赞功能。
<div id="root"></div>
<script>
// 维护点赞渲染逻辑的对象
const likeState = {
render(element) {
element.innerHTML = '赞';
}
};
// 维护取消点赞渲染逻辑的对象
const likedState = {
render(element) {
element.innerHTML = '取消'
}
}
class Button {
constructor(container) {
this.liked = false; // 默认为未点赞状态
this.state = likeState; // 设置当前的状态为未点赞
this.element = document.createElement('button');
container.appendChild(this.element);
this.render(); // 初始化渲染
}
setState(state) {
this.state = state; // 修改渲染状态
button.liked = !button.liked; // 修改状态属性
this.render(); // 重新渲染
}
render() {
this.state.render(this.element);
}
}
// 获取按钮对象并添加点击事件
const button = new Button(document.getElementById('root'));
button.element.addEventListener('click', () => {
button.setState(button.liked ? likeState : likedState);
});
</script>
上面代码使用 “状态模式” 统一封装了按钮的类 Button
,传入的参数为渲染按钮的容器元素,按钮类的内部创建按钮并添加到容器元素中,统一管理了点赞状态,点赞渲染对象,如果想要切换状态只需要执行 button
对象提供的 setState
方法通过传入的不同状态对象进行状态切换和页面渲染。
React 组件显示隐藏
在 React
中,经常会出现通过事件切换组件的显示隐藏,最简单的方式是通过类组件状态来控制,但其实也可以使用 “状态模式” 在组件外编写对状态更改的逻辑,这样可以使组件的逻辑更清晰,代码更精简。
import React from 'react';
import ReactDOM from 'react-dom';
// 状态管理对象
const States = {
show() {
console.log('显示 Banner');
this.setState({ isShow: true });
},
hide() {
console.log('隐藏 Banner');
this.setState({ isShow: false });
}
};
class Banner extends React.Component {
state = { isShow: true }
toggle = () => {
const currentState = this.state.isShow ? 'hide' : 'show';
States[currentState] && States[currentState].apply(this);
}
render() {
return (
<div>
{
isShow && (
<nav>导航</nav>
)
}
<button>toggle</button>
</div>
)
}
}
ReactDOM.render(<Banner />, document.getElementById('root'));
在上面代码中组件外部的 States
就是管理切换状态逻辑的对象,就是说 “状态模式” 也可以在框架中单独使用。
有限状态机
其实 “状态模式” 来源一个有限状态机的概念,有限状态机是指一个事物拥有多种状态,但是同一时间只会处于一种状态,可以通过动作来改变当前的状态,在 JavaScript
中拥有第三方模块 javascript-state-machine
专门帮我们来做这件事。
javascript-state-machine 使用
javascript-state-machine
提供一个类,创建实例时传入的参数为一个 options
对象,属性 init
用来定义初始状态值,属性 transitions
用来定义属性变化,methods
用来定义属性发生变化时所触发的钩子。
// 有限状态机对象
const StateMachine = require('javascript-state-machine');
const fsm = new StateMachine({
init: 'solid', // 初始状态(固态)
transitions: [
{ from: 'solid', to: 'liquid', name: 'melt' },
{ from: 'liquid', to: 'solid', name: 'freeze' },
{ from: 'liquid', to: 'gas', name: 'vaporize' },
{ from: 'gas', to: 'liquid', name: 'condense' }
],
methods: {
onMelt() {
console.log('melt');
},
onFreeze() {
console.log('freeze');
},
onVaporize() {
console.log('vaporize');
},
onCondense() {
console.log('condense');
}
}
});
console.log(fsm.state); // solid
console.log(fsm.can('gas')); // false
console.log(fsm.cannot('gas')); // true
console.log(fsm.transitions()); // [ 'melt' ]
console.log(fsm.allTransitions());
// [ 'init', 'melt', 'freeze', 'vaporize', 'condense' ]
console.log(fsm.allStates()); // [ 'none', 'solid', 'liquid', 'gas' ]
fsm.melt(); // melt
console.log(fsm.state); // liquid
console.log(fsm.transitions()); // [ 'freeze', 'vaporize' ]
fsm.state
:当前状态;fsm.can
:查看是否可直接转换到某个状态,参数为要转换的状态值;fsm.cannot
:查看是否不能直接转换到某个状态,参数为要转换的状态值;fsm.transitions
:返回可转换状态的方法列表;fsm.allTransitions
:返回所有状态转换方法列表;fsm.allStates
:返回定义的所有状态。
javascript-state-machine 原理
根据 javascript-state-machine
的用法我们模拟实现最基本的逻辑,构建一个有限状态机,代码如下:
class StateMachine {
constructor(options) {
const {
init = 'none',
transitions = [],
methods = {}
} = options;
this.state = init;
transitions.forEach(transition => {
const { from, to, name } = transition;
this[name] = function () {
if (this.state === from) {
this.state = to;
const method = 'on' + name.slice(0, 1).toUpperCase() + name.slice(1);
methods[method] && methods[method]();
}
}
});
}
}
总结
“状态模式” 实现的有限状态机可以更大限度的让状态变化与状态对象进行解耦,更减少了大量的判断逻辑,最后附上 案例地址。