#设计模式 #枚举 #状态 #方法 #多态 #dyn

methods-enum

提供了两个宏,用于简化使用枚举而不是 dyn Trait 来实现 '状态' 设计模式和其他动态多态的流程。

11 个版本

0.3.2 2023年8月7日
0.3.1 2023年8月1日
0.3.0 2023年7月31日
0.2.4 2022年7月3日
0.1.5 2022年6月21日

#2 in #多态

每月下载量 29 次

MIT/Apache

65KB
854

crates.io Docs.rs

状态设计模式和其他动态多态通常使用 dyn Trait 对象来解决。

enum-matching 比 Trait 对象更简单、更高效,但在此情况下直接使用它会在接口方法中将状态抽象“涂抹”。

提出的宏 impl_match!{...}#[gen(...)] 提供了两种不同的枚举匹配方式,通过将方法按 enum 变体进行视觉分组,使得在状态设计模式和多态问题中使用枚举匹配更加方便。


impl_match! 宏

这是一个类似于项目宏的宏,它可以封装一个状态 enum 声明和一个或多个 impl 块,允许你在这些 impl 的方法体中编写 match-expressions,而不需要 match-arms,将 match-arms 写入相应的 enum 变体。

使用示例

rust-book 的第 17.3 章“实现面向对象设计模式”展示了 Rust 中状态模式的实现,提供了以下行为

pub fn main() {
    let mut post = blog::Post::new();

    post.add_text("I ate a salad for lunch today");
    assert_eq!("", post.content());
    post.request_review(); // without request_review() - approve() should not work
    post.approve();  
    assert_eq!("I ate a salad for lunch today", post.content());
}

通过在 Cargo.toml 中设置

[dependencies]
methods-enum = "0.3.2"

可以这样解决,例如

mod blog {
    pub struct Post {
        state: State,
        content: String,
    }

    methods_enum::impl_match! {

    impl Post {
        pub fn add_text(&mut self, text: &str)  ~{ match self.state {} }
        pub fn request_review(&mut self)        ~{ match self.state {} }
        pub fn approve(&mut self)               ~{ match self.state {} }
        pub fn content(&mut self) -> &str       ~{ match self.state { "" } }

        pub fn new() -> Post {
            Post { state: State::Draft, content: String::new() }
        }
    }

    pub enum State {
        Draft:          add_text(text)   { self.content.push_str(text) }
                        request_review() { self.state = State::PendingReview },
        PendingReview:  approve()        { self.state = State::Published },
        Published:      content()        { &self.content }
    }

    } // <-- impl_match!
}

这个宏所做的一切就是为所有 enum 变体分支在方法体中标记为 ~ 的未完成的 match-expressions 完成匹配
(EnumName)::(Variant) => { match-ARM模块从枚举 声明 }.
如果一个{}块(没有=>)被设置在未完成的匹配表达式末尾,它将被放置在所有没有此方法在enum中的变体分支中。
(EnumName)::(Variant) => {默认match-ARM模块}.
因此,您可以看到编译器将接收到的所有代码,但以按设计模式结构化的形式。

rust-analyzer[^rust_analyzer]完美地定义了所有块中的标识符。所有在IDE中的提示、自动完成和替换都在显示在enum中的match-arm中处理,就像它们在本地match-block中一样。此外,“内联宏”命令在IDE中也可以使用,显示结果代码。

[^rust_analyzer]:在nightly版或旧版rust版本下运行时,rust-analyzer可能无法展开proc-macro。在这种情况下,建议在它的设置中设置:["rust-analyzer.server.extraEnv": { "RUSTUP_TOOLCHAIN": "stable" }]

其他功能

  • 您还可以在宏中包含impl (Trait) for ...块。在枚举中指定Trait的名称(不带路径),在相应的arm-block之前。以下是一个使用Display的示例。

  • 那里还展示了泛型方法的示例:mark_obj<T: Display>()
    有关泛型的非关键细微差别,请参阅文档

  • @ - 在enum声明之前的字符,例如:@enum Shape {...禁用了传递到编译器:只有match-arms将被处理。如果此enum已在代码的其他地方声明,包括宏之外,则可能需要此操作。

  • 如果您正在使用带有字段enum,则在使用它们的名称之前,指定分解字段的模板(IDE[^rust_analyzer]完全正确地处理此类变量)。接受的分解模板由相同枚举变体的下游方法使用,并且可以重新分配。示例

methods_enum::impl_match! {

enum Shape<'a> {
//     Circle(f64, &'a str),                  // if you uncomment or remove these 4 lines 
//     Rectangle { width: f64, height: f64 }, //    it will work the same
// }
// @enum Shape<'a> {
    Circle(f64, &'a str): (radius, mark)
        zoom(scale)    { Shape::Circle(radius * scale, mark) }      // template change
        fmt(f) Display { write!(f, "{mark}(R: {radius:.1})") };     (_, mark)
        mark_obj(obj)  { format!("{} {}", mark, obj) };             (radius, _)
        to_rect()      { *self = Shape::Rectangle { width: radius * 2., height: radius * 2.,} }
    ,
    Rectangle { width: f64, height: f64}: { width: w, height}
        zoom(scale)    { Shape::Rectangle { width: w * scale, height: height * scale } }
        fmt(f) Display { write!(f, "Rectangle(W: {w:.1}, H: {height:.1})") }; {..}
        mark_obj(obj)  { format!("⏹️ {}", obj) }
}
impl<'a> Shape<'a> {
    fn zoom(&mut self, scale: f64)                      ~{ *self = match *self }
    fn to_rect(&mut self) -> &mut Self                  ~{ match *self {}; self }
    fn mark_obj<T: Display>(&self, obj: &T) -> String   ~{ match self }
}

use std::fmt::{Display, Formatter, Result};

impl<'a> Display for Shape<'a>{
    fn fmt(&self, f: &mut Formatter<'_>) -> Result      ~{ match self }
}

} // <--impl_match!

pub fn main() {
    let mut rect = Shape::Rectangle { width: 10., height: 10. };
    assert_eq!(format!("{rect}"), "Rectangle(W: 10.0, H: 10.0)");
    rect.zoom(3.);
    let mut circle = Shape::Circle(15., "");
    assert_eq!(circle.mark_obj(&rect.mark_obj(&circle)), "⭕ ⏹️ ⭕(R: 15.0)");
    // "Rectangle(W: 30.0, H: 30.0)"
    assert_eq!(circle.to_rect().to_string(), rect.to_string());
}
  • 调试标志。它们可以放置在宏非常开始处的括号内的空格中,
    例如:impl_match! { (ns ) ...
    • 标志nssn无论如何 - 如果它们被错误指定,则将替换为编译错误中的枚举变体中方法和特性的语义绑定。
    • 标志! - 如果它们被错误指定,将导致编译错误,但不会移除语义绑定。

gen() 宏

在单个(非 Trait)实现块之前设置宏属性。根据实现块的方法签名,它生成:从参数元组生成参数的 enum,并生成这些方法的 {} 主体,通过调用此 enum 的参数处理方法来实现。
这允许处理方法根据上下文控制方法的行为,包括通过状态来结构化枚举匹配。

使用示例

让我提醒你从 《rust-book》第 17.3 章“实现面向对象设计模式”中的条件。以下行为是必需的

pub fn main() {
    let mut post = blog::Post::new();

    post.add_text("I ate a salad for lunch today");
    assert_eq!("", post.content());
    post.request_review(); // without request_review() - approve() should not work
    post.approve();  
    assert_eq!("I ate a salad for lunch today", post.content());
}

使用宏 #[gen()] 这样解决

mod blog {
    enum State {
        Draft,
        PendingReview,
        Published,
    }

    pub struct Post {
        state: State,
        content: String,
    }

    #[methods_enum::gen(Meth, run_methods)]
    impl Post {
        pub fn add_text(&mut self, text: &str);
        pub fn request_review(&mut self);
        pub fn approve(&mut self);
        pub fn content(&mut self) -> &str;

        #[rustfmt::skip]
        fn run_methods(&mut self, method: Meth) -> &str {
            match self.state {
                State::Draft => match method {
                    Meth::add_text(text) => { self.content.push_str(text); "" }
                    Meth::request_review() => { self.state = State::PendingReview; "" }
                    _ => "",
                },
                State::PendingReview => match method {
                    Meth::approve() => { self.state = State::Published; "" }
                    _ => "",
                },
                State::Published => match method {
                    Meth::content() => &self.content,
                    _ => "",
                },
            }
        }

        pub fn new() -> Post {
            Post { state: State::Draft, content: String::new() }
        }
    }
}

在处理方法(在这种情况下,run_methods)中,只需为每种状态编写哪些方法应该工作以及如何工作。

该宏在文档注释中重复编译器的输出。因此,在 IDE[^rust_analyzer] 中,你始终可以在枚举名称上方的弹出提示中看到生成的 enum 的声明和生成的代码块。

enum popup hint

enum popup: bodies

调用宏的语法

对于方法最多一个返回类型

#[methods_enum::gen()]EnumName , handler_name]

其中

  • EnumName: 自动生成的枚举名称。
  • handler_name: 处理方法名称

对于方法超过一个返回类型

#[methods_enum::gen()]EnumName , handler_name , OutName]

其中

  • OutName: 从返回类型生成的自动枚举的变种名称。

gen() 宏在限制和与方法和它们的输出值交互的易用性方面不如 impl_match!。gen() 的好处是它允许你看到完整的匹配表达式并处理更复杂的逻辑,包括非平凡输入表达式、匹配守卫和来自子状态枚举的嵌套匹配。


许可

您选择的 MIT 或 Apache-2.0 许可证。


无运行时依赖项