一种基于状态机的代码设计

最近在学习 SwiftUI ,写了一个计算器的 Demo。写完后学习并理解到一种代码设计,感受颇有收获,在此记录一下。git

一句话归纳

咱们将 App 看成一个状态机,状态机的状态决定 App 的界面。首先咱们理出 App 都有什么状态?每一个状态须要什么样的条件会触发什么样的 Action?Action 执行后状态机会进入什么样的次态?理清这些后,根据设计,随着状态机状态的改变,,App 的界面天然会跟着更新。github

以计算器为例

在这里我用我写的计算器 Demo 为例讲解下这种方式。swift

找出状态

计算器的业务逻辑就是接受用户的输入的算式,而后计算并输出算式的结果。那么计算器的状态就是由算式的状态来决定。markdown

首先看看算式的组成:app

“左侧数字 + 计算符号 + 右侧数字 + 计算符号或者等号”oop

看着算式的结构,咱们就能捋出计算器都有什么状态:学习

  1. 计算器正在输入左侧数字,这个状态下用户按了计算符号按钮后会进入下一个状态。
  2. 计算器已经输入了左侧数字和计算符号,开始等待右侧符号
  3. 计算器已经输入了左侧数字和计算符号,正在接收右侧数字(注意和 2 的区别,一个是符号,一个是数字
  4. 计算器已经计算出结果,开始等待符号(可能为新的计算,也可能以这次结果继续进行计算)
  5. 输入或计算出现错误,没法继续

因此咱们须要在代码中定义出五个状态:编码

// 这五个枚举和上面定义的状态按顺序一一对应
enum CalculatorState {
  case left(String)
  case leftOp(left: String, op: Calculator.Op)
  case leftOpRight(left: String, op: Calculator.Op, right: String)
  case equal(value: String)
  case error
}
复制代码

状态咱们已经定义出来了,接下来就须要定义改变状态时须要的 Action 了。spa

须要什么样的 Action 才能进入状态

这里主要以状态 1(left)为例,讲解在状态 1 下遇到什么条件会触发什么样的 Action,而后会进入哪一个状态。设计

  1. 若是接收到了数字的输入,此时状态并不会发生改变,仍是处于 left,只不过这个状态下的数据会发生改变
  2. 若是接收到了运算符的输入,此时就会进入状态 2(leftOp),并且状态 2 的数据是状态 1 的值加上刚刚输入的运算符(Calculator.Op)
  3. 若是接收到了等号,一个算式的生命周期会结束,进入状态 4(equal),等待从新开始一次计算。

语言是苍白的,直接看代码:

// 因为代码过长,所有附上会影响篇幅,此处仅附上部分代码。详细代码能够在文末找到连接
func apply(item: CalculatorButtonItem) -> CalculatorState {
  switch item {
    case .digit(let num):
    return apply(num: num)
    case .dot:
    return applyDot()
    case .op(let op):
    return apply(op: op)
    case .command(let command):
    return apply(command: command)
  }
}

private func apply(num: Int) -> CalculatorBarin {
  switch self {
    case .left(let left):
    return .left(left.apply(num: num))
    case .leftOp(let left, let op):
    return .leftOpRight(left: left, op: op, right: "0".apply(num: num))
    case .leftOpRight(let left, let op, let right):
    return .leftOpRight(left: left, op: op, right: right.apply(num: num))
    case .equal(_):
    return .left("0".apply(num: num))
    case .error:
    return .left("0".apply(num: num))
  }
}
复制代码

上面只解释了状态 1 进入次态须要的条件和进入次态前会作的 Action,其余的状态其实都是相似的,此处就再也不赘述。

其余场景

计算器的业务比较简单,用来阐述代码设计会清晰易懂一些。这种代码设计并非万金油,哪里都能用,不过确定也有不少比较适用的场景。好比应用商店的详情页面,详情页面的状态就比较多,并且状态和状态之间的区别比较清晰,用这种方式来处理就比较合适。

局限

固然这种设计也是有他的局限所在。

好比当业务比较复杂的时候可能会由于发生状态数量爆炸,此时代码应该就会失控了。其余的缺陷暂时尚未想到,等我进行一次最佳实践后可能会发现更多的问题。

天底下不存在一个绝对正确的设计,都须要根据实际状况本身作出相应的调整,以不变应万变~

最后

本文完整代码地址:github.com/T-Oner/Swif…

我在学习 SwiftUI,如何你也对这个感兴趣,那么我推荐这本我在用的教材:

扫描二维码查看教材

本文仅为我的意见,不构成编码建议。

相关文章
相关标签/搜索