Rust·设计模式·状态变化模式

 

state模式:允许一个对象在内部状态改变时,改变其行为 Memento模式:保存原发器(Originator)对象的内部状态, 需要时恢复原发器的状态

  • state模式:允许一个对象在内部状态改变时,改变其行为
  • Memento模式:保存原发器(Originator)对象的内部状态, 需要时恢复原发器的状态

状态变化模式

  • 在组件构建过程中,有些状态经常变化
    • 如何对这些变化进行有效的管理?
    • 同时又维持高层模块的稳定?
  • 典型模式
    • State
    • Memento

状态模式 State

  • 某些对象的状态如果发生改变,其行为也会发生改变
    • 文档处于只读状态,其行为与读写状态时不同
  • 如何在运行时,根据对象的状态透明的改变对象的行为?
    • 而不会在对象的状态变化和行为改变之间引入耦合?

模式定义

  • 允许一个对象在其内部状态发生改变时改变其行为,使这个对象看上去就像改变了它的类型一样
  • 如水一般,状态即事物所处的某一种形态。
  • 状态模式是说一个对象在其内部状态发生改变时,其表现的行为和外在属性不一样,这个对象看上去就像改变了它的类型一样
  • 因此,状态模式又称为对象的行为模式
    • 状态模式的核心思想就是一个事物(对象)有多种状态
    • 在不同的状态下所表现出来的行为和属性不一样

结构

State

初始设计

  • 网络应用
    • 根据网络状态做调整
    • 在一个状态下执行操作后,会跨越到另一个状态

网络应用

enum NetworkState {
    Network_Open,
    Network_Close,
    Network_Connect,
};

class NetworkProcessor{
    NetworkState state;
public:
    void Operation1(){
        if (state == Network_Open){
            //**********
            state = Network_Close;
        }
        else if (state == Network_Close){
            //..........
            state = Network_Connect;
        }
        else if (state == Network_Connect){
            //$$$$$$$$$$
            state = Network_Open;
        }
    }

    public void Operation2(){
        if (state == Network_Open){
            //**********
            state = Network_Connect;
        }
        else if (state == Network_Close){
            //.....
            state = Network_Open;
        }
        else if (state == Network_Connect){
            //$$$$$$$$$$
            state = Network_Close;
        }
    }
    public void Operation3(){

    }
};
  • 策略模式与这种情形很像
  • 当枚举类型增加了新状态,就需要修改很多原来的代码
    • 对修改封装
    • 对扩展开放

重构

  • 将枚举类型变成抽象基类

    • 原来

      enum NetworkState {
          Network_Open,
          Network_Close,
          Network_Connect,
      };
      
    • 重构

      class NetworkState{
      }
      
  • 把所有与状态相关的操作,都变成状态对象的行为

    class NetworkState{
    public:
        // 放一个状态对象的指针
        NetworkState* pNext;
        virtual void Operation1()=0;
        virtual void Operation2()=0;
        virtual void Operation3()=0;
    
        virtual ~NetworkState(){}
    };
    
  • 设计Open/Close/Connect态对象

    class OpenState :public NetworkState{
        static NetworkState* m_instance;
    public:
        // 单例:状态就一个,不需要多个状态
        static NetworkState* getInstance(){
            if (m_instance == nullptr) {
                m_instance = new OpenState();
            }
            return m_instance;
        }
    
        void Operation1(){
            //**********
            // 换成Close态时,换的是一个对象,而不是一个枚举
            // 把跟状态相关的操作,都封装到一个状态对象中
            pNext = CloseState::getInstance();
        }
        void Operation2(){
            //..........
            pNext = ConnectState::getInstance();
        }
        void Operation3(){
            //$$$$$$$$$$
            pNext = OpenState::getInstance();
        }
    };
    
  • 网络应用部分

    class NetworkProcessor{
        NetworkState* pState;
    public:
        NetworkProcessor(NetworkState* pState){
            this->pState = pState;
        }
          
        void Operation1(){
            //...
            /**********************************
             虚函数的本质就是运行时的if..else..
             在运行时判断pState指向的是Open态的对象
             就会调用Open对象的Ope1()
            **********************************/
            pState->Operation1();
            pState = pState->pNext;
            //...
        }
          
        void Operation2(){
            //...
            pState->Operation2();
            pState = pState->pNext;
            //...
        }
          
        void Operation3(){
            //...
            pState->Operation3();
            pState = pState->pNext;
            //...
        }
    };
    
  • 优点

    • 当状态增加的时候,只需增加一个状态类

      class WaitState: public NetworkState {...}
      
    • 而网络应用部分的代码不需要改变

      • 因为状态行为内部自己管理状态的转变
      • 应用部分不涉及状态改变

要点总结

  • State模式把所有与一个特定状态相关的行为都放入一个State子类对象中
    • 在对象状态切换时,切换对应的对象
    • 同时维持State的接口
    • 实现了具体操作(行为)与状态转换之间的解耦
  • 为不同的转换引入不同的对象
    • 使状态转换变得更加明确
    • 而且可以保证不会出现状态不一致的情况
      • 因为转换是原子性的
      • 即要么彻底转换过来,要么不转换
  • 如果State对象没有实例变量
    • 各上下文可以共享(Singleton)同一个State对象
    • 从而节省对象开销

Rust实现

  • 用程序来模拟水的三种不同状态及相互转化
    • 设置水的状态为液态、固态和气态
    • 调用water.behavior()可以实现不同的动作

状态模式的类图

use std::collections::HashMap;

trait IState {
    fn get_name(&self) -> &str;
    fn is_match(&self, stateinfo: i32) -> bool;
    fn behavior(&self);
}

struct SolidState {
    name: String,
}
impl SolidState {
    fn new() -> Self { 
      Self { name: "固态".to_string(), }
    }
}
impl IState for SolidState {
    fn get_name(&self) -> &str {
        &self.name
    }
    fn behavior(&self) {
        println!("[状态类]:{}", self.get_name());
    }
    fn is_match(&self, stateinfo: i32) -> bool {
        return stateinfo <= 0;
    }
}

struct LiquidState {
    name: String,
}
impl LiquidState {
    fn new() -> Self {
        Self { name: "液态".to_string(), }
    }
}
impl IState for LiquidState {
    fn get_name(&self) -> &str {
        &self.name
    }
    fn behavior(&self) {
        println!("[状态类]:{}", self.get_name());
    }
    fn is_match(&self, stateinfo: i32) -> bool {
        return stateinfo > 0 && stateinfo <= 100;
    }
}

struct GasState {
    name: String,
}
impl GasState {
    fn new() -> Self {
        Self { name: "气态".to_string(), }
    }
}
impl IState for GasState {
    fn get_name(&self) -> &str {
        &self.name
    }
    fn behavior(&self) {
        println!("[状态类]:{}", self.get_name());
    }
    fn is_match(&self, stateinfo: i32) -> bool {
        return stateinfo > 100;
    }
}

trait IContext {
    fn get_state(&self) -> usize;
    fn set_state(&mut self, state: usize);
    fn set_stateinfo(&mut self, stateinfo: i32);
    fn get_stateinfo(&self) -> i32;
}

struct Water {
    // 使用HashMap来保存状态类,用于查询和删除
    states: HashMap<usize, Box<dyn IState>>,
    cur_state: usize,
    stateinfo: i32,
}
impl Water {
    fn new() -> Self {
        Self {
            states: HashMap::new(),
            cur_state: 0,
            stateinfo: 0,
        }
    }
    fn init(&mut self) {
        self.add_state(Box::new(SolidState::new()));
        self.add_state(Box::new(LiquidState::new()));
        self.add_state(Box::new(GasState::new()));
        self.set_tmpr(-40);
    }
    fn add_state(&mut self, state: Box<dyn IState>) {
        let len = self.states.len();
        self.states.insert(len, state);
    }

    fn get_tmpr(&self) -> i32 {
        self.get_stateinfo()
    }
    fn set_tmpr(&mut self, tmpr: i32) {
        println!("[水元素] 设置水温:{} 度", tmpr);
        self.set_stateinfo(tmpr);
    }
    fn rise_tmpr(&mut self, step: i32) {
        self.set_stateinfo(self.get_tmpr() + step);
        println!("[水元素] 升温至:{} 度", self.get_tmpr());
    }
    fn _reduce_tmpr(&mut self, step: i32) {
        self.set_stateinfo(self.get_tmpr() - step)
    }
    fn behavior(&self) {
        let state = self.get_state();
        self.states.get(&state).as_ref().unwrap().behavior();
    }
}

impl IContext for Water {
    fn set_state(&mut self, idx: usize) {
        let state = self.cur_state;
        if self.cur_state == idx {
            return;
        }
        println!(
            "[IContext]: {} -> {}",
            self.states.get(&state).as_ref().unwrap().get_name(),
            self.states.get(&idx).as_ref().unwrap().get_name()
        );
        self.cur_state = idx;
    }

    fn get_state(&self) -> usize {
        self.cur_state
    }

    fn set_stateinfo(&mut self, stateinfo: i32) {
        self.stateinfo = stateinfo;
        let mut state_idx = 0;
        for (idx, state) in self.states.iter() {
            if state.is_match(stateinfo) {
                state_idx = *idx;
                break;
            }
        }
        self.set_state(state_idx);
    }

    fn get_stateinfo(&self) -> i32 {
        self.stateinfo
    }
}

fn main() {
    let mut water = Water::new();
    water.init();

    for _ in 0..5 {
        water.rise_tmpr(30);
        water.behavior();
    }
}

执行结果:

[水元素] 设置水温:-40 度
[水元素] 升温至:-10 度
[状态类]:固态
[IContext]: 固态 -> 液态
[水元素] 升温至:20 度
[状态类]:液态
[水元素] 升温至:50 度
[状态类]:液态
[水元素] 升温至:80 度
[状态类]:液态
[IContext]: 液态 -> 气态
[水元素] 升温至:110 度
[状态类]:气态
  • 上面的应用场景是Water水元素
  • 水元素实现了IContext接口,它关联(使用)了IState
  • 水元素通过state.behavior()来执行状态类的行为
  • IContext和IState都是固定不变的,可以单独设计
  • 依赖它们的Water,SolidState/LiquidState是可变的,可以根据应用场景随时变化

Memento 备忘录模式

动机

  • 某些对象在状态转换过程中
    • 要求能回溯到对象之前的某个状态
  • 如何实现对象状态的良好保存与恢复?
    • 同时又不会破坏对象本身的封装?

模式定义

  • 在不破坏封装的前提下
    • 捕猎一个对象的内部状态
    • 并在该对象之外保存这个状态
    • 这样以后可以将该对象恢复到原先保存的状态

结构

初始设计

  • 要保存对象状态的类

    class Originator {
        string state;
        //.... 其他内部状态
    };
    
    • 需要给它拍一个内部的快照
  • 设计另一个类:Memento

    class Memento {
        string state;
        //.. 内部状态和 Originator 对应
    public:
        Memento(const string & s) : state(s) {}
        string getState() const { return state; }
        void setState(const string & s) { state = s; }
    };
    
    • 它内部状态和Originator是对应的
  • 再给Originator增加Memento功能

    class Originator {
        string state;
        //.... 其他内部状态
    public:
        /******************************
         * 创建了一个Memento的类
         * 相当于给当前内存状态拍照
        ******************************/
        Originator() {}
        Memento createMomento() {
            Memento m(state);
            return m;
        }
        void setMomento(const Memento & m) {
            state = m.getState();
        }
    };
    
  • 客户端使用

    int main() {
        Originator orginator;
          
        //捕获对象状态,存储到备忘录
        Memento mem = orginator.createMomento();
          
        //... 改变orginator状态
    
        //从备忘录中恢复
        orginator.setMomento(mem);
    }
    

要点总结

  • 备忘录(Memento)保存原发器(Originator)对象的内部状态
    • 需要时恢复原发器的状态
  • Memento模式的核心是隐藏信息
    • 即Originator要向外隐藏信息,保持其封装性
    • 同时又要把状态保存到外界
  • 现代语言都有序列化功能
    • 往往采用效率高、容易正确使用的序列化方案来实现Memento