[Rust笔记] 对照 OOP 浅谈【类型状态】设计模式

对照OOP浅谈【类型状态】设计模式

类型状态·设计模式Type State Pattern也被称作“泛型·即是·类的类型(约束)Generic as Type Class (Constraint)”。它是基于Rust独有语言特性

  • 单态化monomorphization

  • move赋值语义

的新颖设计模式。其中,【move赋值语义】为Rust所独有的原因是

  • 一方面,GC类计算机语言已经将内存托管给vm,所以他们就没再搞出这类复杂的内存管理概念,和增加开发者的心智负担。

  • 另一方面,Cppmove赋值语义仅只是对历史包袱的【妥协方案】。这体现在

    所以,和Rust相比,Cppmove赋值语义至多就是一个“弟弟”。其功能相当于Rust标准库提供的std::mem::take(&T) -> T内存操作 — 使用【类型·默认值】置换出【引用】内存位置上的值;同时,保留·原变量·的【所有权】不被消耗掉和可以被接着使用。

    • Cppmove语义是: 用空指针nullptr换走原变量的值;但,原变量依旧可访问。这哪里是move,分明是swap呀!

    • Rustmove语义是:拿走原变量的值;同时,作废原变量。这个操作也被称为“消耗consuming”。

  • 此外,move也不是Cpp变量赋值的默认语义。相反 ,开发者得显示地编码std::move(ptr)函数调用和将lvalue转换为rvalue

    Cppstd::move(ptr)函数调用是【零·运行时·成本】的。在编译之后,编译器会将其从机器码内扣掉。其“辅助线”般的功能有些类似于Rust中的std::marker::PhantomData<T>

名词解释

为了避免后续文章内容过于啰嗦,首先定义五个词条,包括:

  • 泛型类型

  • 泛型类型参数

  • 泛型类型

  • 泛型类型

  • 泛型类型参数限定条件

一图抵千词,大家来看下图吧:

aeb3d0129fa9ac6eea736720385f634f.png

基本概念

【类型状态·模式】的初衷是:将【运行时】对象状态的(动态)信息·编码入·【编译时】对象类型的(静态)定义里。进而,借助现成且完备的Rust【类型系统】,在【编译】过程中,确保:

  1. 处于不同状态的(泛型类型)实例·拥有不一样的(【成员方法】+【关联函数】+【字段】)集合。

  2. (泛型类型)实例·仅能在毗邻的状态之间进行“状态·过渡”,而不能“跳变”。

  3. 排查出·状态的成员方法调用。比如,A状态的实例调用了仅在B状态才有效的成员方法。 而不是,让这类错误潜伏着和等【测试覆盖】或抛出【运行时·异常】。

以【订单系统】为例,【编译器】就能筛查出代码里

  • 对【无效订单】实例的【发货】成员方法调用

  • 对【出库订单】实例的【完成】成员方法调用 — 还未经历【发货】与【收款】两个状态

相对于传统的OOP程序,Rust【类型状态】设计模式将【对象·状态】的【运行时】检查前置于【编译环节】。进而带来的好处包括但不限于:

  • 将【运行时】程序崩溃“无害化”为【编译时】错误。

    • 就开发者而言,这意味着更短的【思考+试错】反馈回路。

    • 就应用程序而言,这意味着更高的性能,更健壮的可靠性,和更重的应用程序大小 — 【单态化】的本质就是以空间换时间

  • 允许IDE提供更有价值的代码提示。即,仅智能地列出对当前状态实例有效的【成员方法】,而不是罗列全部成员方法。比如,当开发者“点”一个【无效订单】实例时,IDE就不应该提示出【发货】成员方法。这才是对开发者最实在的帮助。

代码套路

从操作细节来说,为了采用【类型状态·设计模式】,我们需要:

  1. 将每个【状态】分别映射为独立的【结构体】(比如,struct State1)。和在结构体内,定义【状态】独有的:字段。(见伪码#1注释)

    1. 而不是,使用一个【枚举类】enum State {...}笼统地描述所有【状态】

    2. 后文称这类【结构体】为【状态·类型】。

  2. 以【泛型·类型】+【泛型·类型·参】的结构体定义(比如,struct Type1<S1>),抽象所有【状态】共有的:字段。(见伪码#2注释)

  3. 以【泛型·类型】+【泛型·类型·参】的实现块(比如,impl<S1> Type1<S1>),抽象所有【状态】共有的:成员方法,关联函数,关联常量,和关联类型。(见伪码#3注释)

  4. 以【泛型·类型】+【泛型·类型·参】的实现块(比如,impl Type1<State1>),定制每个【状态】独有的:成员方法,关联函数,关联常量,和关联类型。(见伪码#4注释)

/// #1 【状态·类型】
struct State1 {
    private_field1: String // 定义【状态】独有【字段】
}
struct State2 {
    private_field2: String // 定义【状态】独有【字段】
}
/// #2 【泛型·类型】+【泛型·类型·形参】
struct Type1<S1> {     // <- 被参数化的【状态·类型】既作为【泛型·类型·参数】,
    state: S1,         // <- 也作为【状态·字段】的字段类型
    com_field0: String // 抽象全部【状态】共有【字段】
}
/// #3 【泛型·类型】+【泛型·类型·形参】
impl<S1> Type1<S1> { // 抽象全部【状态】共有的【成员方法】
    fn com_function0(&self) -> String {
        "全状态可调用".to_string()
    }
}
/// #4 【泛型·类型】+【泛型·类型·实参】
impl Type1<State1> { // 定制【状态】`State1`独有【成员方法】
    fn private_function1(&self) -> String {
        "仅 State1 状态可调用".to_string()
    }
}
impl Type1<State2> { // 定制【状态】`State2`独有【成员方法】
    fn private_function2(&self) -> String {
        "仅 State2 状态可调用".to_string()
    }
}

承上段代码,在【泛型·类型】struct Type1<S1>中,被参数化的【状态·类型】S1既作为【泛型·类型·参数】也作为【状态·字段】state的字段类型(这是由Generic Struct定义要求的 — 在结构体定义中,被声明的泛型参数必须被使用)。

// 继续前面的代码
let type1_state1 = Type1 {
    com_field0: "对所有状态都看得到的,共用字段值
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值