目录

浅尝有限状态机

场景

实现一个 Connection 连接类,它有 openreadwirteclose 四个函数,下面是这四个函数的限制条件:

  • read 需要在 open 后才能使用,否则抛出 Not Open 异常
  • wirte 需要在 open 后才能使用,否则抛出 Not Open 异常
  • open 不能在 open 状态下使用,否则抛出 Already Open 异常
  • close 不能在 close 状态下使用,否则抛出 Already Closed 异常

如何实现这个类?

方案一:在函数前置判断语句用来判断

最通常的做法是在 openreadwirteclose 函数前部加一些状态判断条件,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Connection {
  state = "CLOSED";

  read = () => {
    if (this.state !== "OPEN") {
      throw new Error("Not Open");
    }
    console.log("reading");
  };

  write = (content) => {
    if (this.state !== "OPEN") {
      throw new Error("Not Open");
    }
    console.log("writing:", content);
  };

  open = () => {
    if (this.state === "OPEN") {
      throw new Error("Already Open");
    }
    this.state = "OPEN";
  };

  close = () => {
    if (this.state === "CLOSED") {
      throw new Error("Already Closed");
    }
    this.state = "CLOSED";
  };
}

上面的状态比较少,所以判断还不算很复杂;不过如果有更多的状态,前部的判断条件语句会变得非常复杂且难以维护。

方案二:使用有限状态机

另一个方案是使用有限状态机,它的核心思想是为每一个状态都抽象成一个状态类,这个状态类继承一个基础的状态类,并且在自身实现当前状态能够操作的函数,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class ConnectionState {
  read = () => {
    throw new Error("Not Open");
  };

  write = () => {
    throw new Error("Not Open");
  };

  open = () => {
    throw new Error("Already Open");
  };

  close = () => {
    throw new Error("Already Closed");
  };
}

class ClosedConnectionState extends ConnectionState {
  open = (_this) => {
    _this.state = new OpenConnectionState();
  };
}

class OpenConnectionState extends ConnectionState {
  read = () => {
    console.log("reading");
  };

  write = (content) => {
    console.log("writing:", content);
  };

  close = (_this) => {
    _this.state = new ClosedConnectionState();
  };
}

class Connection {
  state;

  constructor() {
    this.init();
  }

  init = () => {
    this.makeNewState(new ClosedConnectionState());
  };

  makeNewState = (state) => {
    this.state = state;
  };

  read = () => {
    return this.state.read(this);
  };

  write = (content) => {
    return this.state.write(content);
  };

  open = () => {
    return this.state.open(this);
  };

  close = () => {
    return this.state.close(this);
  };
}

可以看到,上面的写法中没有用一行 if 条件判断,就实现了状态的转移和判断。

上面的写法比第一种写法代码量多了不少,不过可维护性却大大增加了,这个优势在状态更多时更能体现。

参考文档