资讯专栏INFORMATION COLUMN

前端设计模式用起来(1)状态模式

Salamander / 3130人阅读

摘要:有限状态机可以归纳出四个要素现态即当前的状态。但状态模式还有一点需要注意到,当采用子类继承实现多种具体状态的时候,注意控制状态的数量,以免出现子类数量膨胀的现象在使用或等更完整面向对象语言时。

业务代码开发久了,偶尔看看设计模式,总会让自己有一种清新脱俗的感觉。总想把这种感觉记下来,但一想到要先起个恰如其分的标题和开头,就让我有一种百爪挠心的纠结,所以迟迟没有开始。今天起更新我学习设计模式笔记的原因,就好像是,你喜欢一个女孩久了,却总不表白,难道不怕被别人截胡了么!

首先我们来一起设想一些场景:

程序员们在等电梯的时候,聊天频率最高的一个话题,是不是电梯调度算法呢。写字楼的几部电梯到底是分单双层运力快,还是高低层运力快?当你按下电梯时,是就近楼层的电梯视来接你,还是自顾自的先上后下顺带来接你?

开车来到路口,对红绿灯亮灭的长短是否曾有过习惯性的吐槽

除了电梯调度、红绿灯控制,软件设计和业务开发中,类似诸如状态切换的问题不难遇到。他们的共同点是:场景存在多个状态,状态改变时会触发对应的不同处理方法,状态间切换又存在诸多约束和限制

面对这种场景,你脑海里的第一解决方案是什么?条件分支if...else或者switch...case么?其实应当视具体场景复杂度来看,如果状态少逻辑简单,条件分支力所能及。但倘若状态较多,逻辑复杂,又存在诸多特殊情况的约束限制,本文将介绍的状态模式欢迎来解一下。

状态模式
定义:当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类

我们先来看下传统面向对象语言的类图,后面再细说前端js中该如何应用

对于一个if...else处理的长流程,我们可以抽象成多个状态的切换,不同具体的状态继承自一个抽象状态接口。场景类维护当前状态对象的一个实例,以及状态切换间的约束关系。

这样做的好处是将状态的获取和状态的切换进行了分离,一个具体的状态类只处理本状态相关的逻辑,符合单一职责原则,后期如果新加状态只需要新建具体的状态类,符合开放封闭原则

JavaScript没有抽象接口类的概念,所有类图也大大简化,如下:

State类为状态类,包含状态值以及状态改变时具体的处理方法。Context类为场景类,维护状态间的约束关系以及控制触发状态的切换。
下面我们看下代码,让概念平稳落地:

// 定义状态类
class State {
    constructor(color) {
        this.color = color
    }
    // 处理该状态下具体逻辑
    handle(context) {
        console.log(`turn to ${this.color}`)
        context.setState(this)
    }
}
// 定义场景类
class Context {
    constructor() {
        this.state = null
    }
    setState(state) {
        this.state = state
    }
    getState() {
        return this.state
    }
}

测试代码如下:

const ctx = new Context()
// 实例出具体状态
const red = new State("red")
const green = new State("green")
const yellow = new State("yellow")
// 绿灯亮
green.handle(ctx)
console.log(ctx.getState())
// 红灯亮
red.handle(ctx)
console.log(ctx.getState())
// 黄灯亮
yellow.handle(ctx)
console.log(ctx.getState())
有限状态机

状态模式脱胎自有限状态机(Finite-State-Machine),这个数学模型描述了有限个状态,以及这些状态之间转移和动作的行为,是一种对象行为建模的工具。类似下图就是一种有限状态机:

其实我们在处理业务逻辑时,经常打交道的各种事件和状态切换,写的各种if...elseswitch...case都是有限状态机模型,只是平时没有意识到吧了。在处理较为复杂的逻辑时,考虑把业务逻辑抽象成一个有限状态机模型,常常会是代码逻辑清晰,结构规整。

有限状态机可以归纳出四个要素:

现态:即当前的状态。

条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。

动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。

次态:条件满足后要迁往的新状态。次态是相对于现态而言的,次态一旦被激活,就转变成新的现态了。

Tips:避免把某个程序动作当作是一种状态来处理,动作是不稳定的,即使条件没有触发,一旦动作执行完也就结束了;但状态是稳定的,如果没有外部条件触发,状态会一直持续下去。

介绍了有限状态机,我们当然可以通过上面介绍的状态模式的方式,来将这种模型工具应用到我们的代码开发当中。但是你有没有注意到一个问题?对,代码不够优雅,略显简陋,不能忍!

接下来介绍一个优雅的有限状态机实现类库javascript-state-machine,接下来使用这个类库简单实现一个Promise的功能,来看一下如何使用。

Promise简单实现

首先回顾一下Promise的特点:

Promise是一个类。

Promise在实例初始化的时候需要传入一个函数。

传入的函数需要接收resolvereject两个函数,成功的时候调用resolve,失败的时候调用reject

Promise实例出的对象有一个then方法,可以进行链式操作。

Promise拥有三种状态:pendingfulfilledrejected,可以从pending->fulfilled,或pending->rejected,但不能逆向。

接下来上代码实现一下

// 状态机模型
const fsm = new StateMachine({
    // 初始状态
    init: "pending",
    // 状态迁移规则,name,from,to的名字尽量别同名
    transitions: [
        { name: "resolve", from: "pending", to: "fulfilled" },
        { name: "reject", from: "pending", to: "rejected"}
    ],
    methods: {
        onResolve(state, data) {
            data.successFn.forEach(fn => fn())
        },
        onReject(state, data) {
            data.failFn.forEach(fn => fn())
        }
    }
})
// 定义Promise
class MyPromise {
    constructor(fn) {
        this.successFn = []
        this.failFn = []
        fn(
            () => { fsm.resolve(this)},
            () => { fsm.reject(this)}
        )
    }
    then(successFn, failFn) {
        this.successFn.push(successFn)
        this.failFn.push(failFn)
        return this
    }
}
总结

本文介绍了状态模式和有限状态机的概念,以及才开发中优雅使用的姿势javascript-state-machine,并通过用其简单实现了Promise的基本功能,演示了如何使用。

其实重点还是状态模式,通过本文介绍可以很明显地感受到其优点:

结构清晰,避免了过多的if...elseswitch...case的使用,降低了程序的复杂性,提高了系统的可维护性。

很好遵循了开放封闭原则和单一职责原则。

记得Martin在《重构》中,提到一个坏的代码味道“Long Method”,当你遇到一个方法中包含了一大堆逻辑,做了很多事的时候,你就应该嗅探到一股恶臭味,怎么去修改,或许考虑使用状态模式是一条途径。

但状态模式还有一点需要注意到,当采用子类继承实现多种具体状态的时候,注意控制状态的数量,以免出现子类数量膨胀的现象(在使用TypeScriptJava等更完整面向对象语言时)。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/99129.html

相关文章

  • JavaScript系列(四) - 收藏集 - 掘金

    摘要:函数式编程前端掘金引言面向对象编程一直以来都是中的主导范式。函数式编程是一种强调减少对程序外部状态产生改变的方式。 JavaScript 函数式编程 - 前端 - 掘金引言 面向对象编程一直以来都是JavaScript中的主导范式。JavaScript作为一门多范式编程语言,然而,近几年,函数式编程越来越多得受到开发者的青睐。函数式编程是一种强调减少对程序外部状态产生改变的方式。因此,...

    cfanr 评论0 收藏0
  • 前端开发中常的javascript设计模式

    摘要:代理模式,迭代器模式,单例模式,装饰者模式最少知识原则一个软件实体应当尽可能少地与其他实体发生相互作用。迭代器模式可以将迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即不用关心对象内部构造也可以按顺序访问其中的每个元素。 接手项目越来越复杂的时候,有时写完一段代码,总感觉代码还有优化的空间,却不知道从何处去下手。设计模式主要目的是提升代码可扩展性以及可阅读性。 本文主要以例子的...

    赵春朋 评论0 收藏0
  • 观众老爷们,来试试这个 Vue 撸的数据可视化后台吧

    摘要:至于如何优雅地管理使用,再次祭出潘神的文章手摸手,带你优雅的使用掘金项目的后端接口文档我是用的进行的管理,其实有很多强大的功能,不仅仅是一个接口测试工具,接口文档管理就是其中一个。 首先放个线上地址大家感受一下(由于后端用的是 leancloud 的免费套餐,因此可能会比较慢): vue-data-board P.S. 建议大家尽量自己注册一个账号(可以随便填一个密码),如果用默认的测...

    JinB 评论0 收藏0
  • 前端状态管理请三思

    摘要:它们是单向数据流和状态容器,而不是状态管理。几个月之前我开始寻找可以解决状态管理问题的模式,最终我发现了状态机的概念。状态机不接受没有明确定义的输入作为当前的状态。状态机强制开发者以声明式的方式思考。 最近我开始思考React应用的状态管理。我已经取得一些有趣的结论,并且在这篇文章里我会向你展示我们所谓的状态管理并不是真的在管理状态。 译者:阿里云前端-也树 原文链接:managing...

    魏宪会 评论0 收藏0
  • JS 状态模式

    摘要:简介状态模式允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。状态通常为一个或多个枚举常量的表示。简而言之,当遇到很多同级或者的时候,可以使用状态模式来进行简化。 1. 简介 状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。其实就是用一个对象或者数组记录一组状态,每个状态对应一个实现,实现的时候根据状态挨个去运...

    xingqiba 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<