rust 狀態機設計模式

什麼是狀態機

狀態機無處不在,像咱們經常使用的 tcp、http、regexp 等等本質上都是狀態機。設計模式

狀態機是由狀態和狀態間的轉換構成的。app

拿紅綠燈來舉個簡單的例子,紅綠燈會處於 3 個狀態:紅、綠、黃,這幾個狀態之間有肯定的轉換路徑:tcp

+-----------+      +------------+      +---------+
|   Green   +----->+   Yellow   +----->+   Red   |
+-----+-----+      +------------+      +----+----+
      ^                                     |
      |                                     |
      +-------------------------------------+

若是用 rust 來寫的話,我可能會這樣實現:oop

use std::thread::sleep;
use core::borrow::Borrow;
use std::time::Duration;

// 把紅綠燈當作一個狀態機,狀態轉換過程以下:
//+-----------+      +------------+      +---------+
//|   Green   +----->+   Yellow   +----->+   Red   |
//+-----+-----+      +------------+      +----+----+
//      ^                                     |
//      |                                     |
//      +-------------------------------------+

#[derive(Debug)]
enum TrafficLightState {
    Red { waiting_time: std::time::Duration },
    Yellow { waiting_time: std::time::Duration },
    Green { waiting_time: std::time::Duration },
}

struct TrafficLight {
    state: TrafficLightState,
}

fn change_light(mut state: &TrafficLightState) -> TrafficLightState {
    match state {
        TrafficLightState::Green { waiting_time } => {
            sleep(*waiting_time);
            TrafficLightState::Yellow { waiting_time: std::time::Duration::new(10, 0) }
        },
        TrafficLightState::Red { waiting_time } => {
            sleep(*waiting_time);
            TrafficLightState::Green { waiting_time: std::time::Duration::new(60, 0) }
        },
        TrafficLightState::Yellow { waiting_time } => {
            sleep(*waiting_time);
            TrafficLightState::Red { waiting_time: std::time::Duration::new(60, 0) }
        }
    }
}

fn main() {
    let mut state_machine = TrafficLight{
        state: TrafficLightState::Green { waiting_time: std::time::Duration::new(60, 0) }
    };

    loop {
        println!("{:?}", state_machine.state);
        state_machine.state = change_light(&state_machine.state)
    }
}

初始爲綠色狀態,60s 後切換爲黃色狀態,10s 後切換爲紅色狀態,60s 後又切換回綠色,如此循環往復。設計

這段代碼雖然實現了咱們的需求,可是並非很漂亮。除了存在一些重複的代碼,更嚴重的問題是狀態的變換徹底暴露給了外部,這意味着外部能夠作任意的狀態切換,好比紅色 -> 黃色,黃色 -> 綠色 等等,這並非咱們但願的行爲。並且從職責分離的角度來看,狀態的切換也應該是由一個狀態機根據當前的輸入和一些環境條件自行決定的,這些行爲不須要也不該該讓外部知道。code

如今咱們再來描述一下咱們想要實現的狀態機:regexp

  1. 在某一時刻,狀態機處於一個肯定的狀態
  2. 每一個狀態均可以有其相關的取值
  3. 狀態之間的切換須要有肯定的語義
  4. 容許必定程度的狀態共享
  5. 只容許發生聲明過的狀態切換,不容許出現不明路徑的狀態變換
  6. 從一個狀態切換到另外一個狀態後,就不該該有任何依賴前一個狀態的地方了
  7. 錯誤信息應該容易讓人理解

將狀態控制在狀態機內部

impl TrafficLight {
    fn new() -> Self {
        TrafficLight {
            state: TrafficLightState::Green { waiting_time: std::time::Duration::new(60, 0) }
        }
    }

    fn change_light(&mut self) {
        self.state = match self.state {
            TrafficLightState::Green { waiting_time } => {
                sleep(waiting_time);
                TrafficLightState::Yellow { waiting_time: std::time::Duration::new(10, 0) }
            },
            TrafficLightState::Red { waiting_time } => {
                sleep(waiting_time);
                TrafficLightState::Green { waiting_time: std::time::Duration::new(60, 0) }
            },
            TrafficLightState::Yellow { waiting_time } => {
                sleep(waiting_time);
                TrafficLightState::Red { waiting_time: std::time::Duration::new(60, 0) }
            }
        }
    }
}

咱們使用 rust 的關鍵字 impl 爲類型 TrafficLight 關聯了 new 方法和 change_light 方法。以後外部就能夠直接調用 new 來進行初始化了,而修改狀態只須要調用 change_light 就能夠了。get

使用泛型和類型轉換

面對複雜狀態的轉換時,咱們最好能把全部的狀態和變換路徑都描述出來。咱們能夠利用泛型來描述多種狀態,並使用 From Into 來描述狀態間的變換。it

use std::thread::sleep;
use core::borrow::Borrow;
use std::time::Duration;
use std::cmp::Ordering::Greater;

// 把紅綠燈當作一個狀態機,狀態轉換過程以下:
//+-----------+      +------------+      +---------+
//|   Green   +----->+   Yellow   +----->+   Red   |
//+-----+-----+      +------------+      +----+----+
//      ^                                     |
//      |                                     |
//      +-------------------------------------+

#[derive(Debug)]
struct Red {
    wait_time: Duration
}

impl Red {
    fn new() -> Self {
        Red{
            wait_time: Duration::new(60, 0)
        }
    }
}

#[derive(Debug)]
struct Green {
    wait_time: std::time::Duration
}

impl Green {
    fn new() -> Self {
        Green{
            wait_time: Duration::new(60, 0)
        }
    }
}

#[derive(Debug)]
struct Yellow {
    wait_time: std::time::Duration
}

impl Yellow {
    fn new() -> Self {
        Yellow{
            wait_time: Duration::new(10, 0)
        }
    }
}

#[derive(Debug)]
struct TrafficLight<TLS> {
    state: TLS,
}

impl TrafficLight<Green> {
    fn new() -> Self {
        TrafficLight {
            state: Green::new(),
        }
    }
}

impl From<TrafficLight<Green>> for TrafficLight<Yellow> {
    fn from(green: TrafficLight<Green>) -> TrafficLight<Yellow> {
        println!("last state is {:?}", green);
        sleep(green.state.wait_time);
        TrafficLight {
            state: Yellow::new(),
        }
    }
}

impl From<TrafficLight<Yellow>> for TrafficLight<Red> {
    fn from(yellow: TrafficLight<Yellow>) -> TrafficLight<Red> {
        println!("last state is {:?}", yellow);
        sleep(yellow.state.wait_time);
        TrafficLight {
            state: Red::new(),
        }
    }
}

impl From<TrafficLight<Red>> for TrafficLight<Green> {
    fn from(red: TrafficLight<Red>) -> TrafficLight<Green> {
        println!("last state is {:?}", red);
        sleep(red.state.wait_time);
        TrafficLight {
            state: Green::new(),
        }
    }
}

enum TrafficLightWrapper {
    Red(TrafficLight<Red>),
    Green(TrafficLight<Green>),
    Yellow(TrafficLight<Yellow>),
}

impl TrafficLightWrapper {
    fn new() -> Self {
        TrafficLightWrapper::Green(TrafficLight::new())
    }
    fn step(mut self) -> Self {
        match self {
            TrafficLightWrapper::Green(green) => TrafficLightWrapper::Yellow(green.into()),
            TrafficLightWrapper::Yellow(yellow) => TrafficLightWrapper::Red(yellow.into()),
            TrafficLightWrapper::Red(red) => TrafficLightWrapper::Green(red.into())
        }
    }
}

fn main() {
    let mut state_machine = TrafficLightWrapper::new();

    loop {
        state_machine = state_machine.step();
    }
}

總結

把上面的代碼抽象一下,咱們就能獲得狀態機設計模式,以後解決相似問題,就能夠直接利用這個模型了:io

  1. 定義所有狀態
  2. 定義一個狀態容器,可使用泛型以及實現相應的 from 方法來完成狀態之間的切換
  3. 定義枚舉類型描述所有的可選狀態
  4. 初始化狀態機,調用相應的驅動方法,啓動狀態機

嗯,Rust 真香!

Reference

相關文章
相關標籤/搜索