十五、Rust 中的面向对象

157 阅读5分钟

十五、Rust 中的面向对象

1. OOP 基本特性

  • 面向对象编程:Object-oriented Programing,简称OOP

  • 面向对象三大特性:封装、继承、多态

  • 封装

    • 在 Rust 中,封装 体现在 trait、模块化、结构体、枚举
  • 继承

    • 在 Rust 中,没有体现 继承 的能力
    • 在 Java 的传统设计模式中,有提到一个原则,叫做 “多用组合,少用继承”
    • 在 Rust 中,组合 体现在 trait
  • 多态

    • 同一个行为具有多个不同表现形式或形态
    • 在 Rust 中,多态 体现在 trait
  • 一个练习

    // 定义一个 trait, 用于指定可绘制元素的绘制方法  ==>  这类似于 Java 中的抽象类 (abstract class) 或者接口 (interface)
    pub trait Draw {
        fn draw(&self);
    }
    
    // 绘制器,包含了多个可绘制元素  ==>  这类似于 Java Class 中的属性
    pub struct Screen {
        pub components: Vec<Box<dyn Draw>>,
    }
    
    // 为绘制器实现绘制方法  ==>  这类似于 Java Class 中的方法
    impl Screen {
        pub fn run(&self) {
            for component in self.components.iter() {
                component.draw();
            }
            println!("Screen drew ending.");
        }
    }
    
    // 一个具体的可绘制元素:按钮  ==>  这类似于 Java Class 中的属性
    pub struct Button {
        pub width: i32,
        pub height: i32,
        pub label: String,
    }
    
    // 为这个可绘制元素提供绘制方法的实现  ==>  这类似于 Java Class 实现 (implements) 了指定的接口或继承 (extends) 了指定的抽象类
    impl Draw for Button {
        fn draw(&self) {
            // 实现具体的内容
            println!("... button drew ...");
        }
    }
    
    pub fn oop_test() {
        let screen = Screen {
            components: vec![Box::new(Button {
                width: 32,
                height: 16,
                label: String::from("Button"),
            })],
        };
    
        screen.run();
    }
    
    

2. trait 特性

  • 动态分发 (dynamic dispatch)

    • 编译器在编译时无法知晓具体类型和对应的具体方法,只有在运行时才确定
  • 静态分发 (static dispatch)

    • 常发生在泛型具体化时,编译器会为每一个具体类型替代的泛型,生成非泛型的函数方法实现

      // 例如如下的变量,它对应的泛型类型是 Vec<T>,
      // 在编译时,编译器会用 i32 来替代泛型 T,并为这个变量生成 i32 相关的函数和方法实现
      
      let v = vec![1, 2, 3];
      
      
  • 相比动态分发,静态分发会显得更高效一些

  • 当使用 trait 对象时,Rust 必须使用动态分发,因为编译器无法知道实现了特定 trait 的所有类型

  • 只有 对象安全object safe)的 trait 才可以组成 trait 对象

    • 如果一个 trait 的所有方法都满足如下条件,则这个 trait 是 对象安全
      • 返回值类型不是 Self
      • 没有泛型类型的参数

3. 设计模式之:状态模式

  • 状态模式:一个对象暴露出了一个或多个值,且具有一些内部状态,暴露出的值会随着这些内部状态的变化而变化

  • 优点:当业务需求改变时,无需改变值持有状态或者使用值的代码;需要暴露新的值时,也可直接扩展

  • 缺点:状态之间可能会相互联系,导致一些逻辑的紧耦合;可能会有一些重复逻辑无法复用

  • 一个示例

    // 根据官方教程的一个示例改编的示例
    // #region lib.rs
    trait State {
        // Box<Self>: 这个方法调用只对这个类型的 Box 有效,与 self、 &self 或者 &mut self 完全不同
        /// 从当前状态切换到下一步的状态
        fn apply_for_next_state(self: Box<Self>) -> Box<dyn State>;
        /// 从当前状态切换回上一步的状态
        fn reject_to_last_state(self: Box<Self>) -> Box<dyn State>;
        /// 打印自己
        fn show(&self);
        /// 获取文章的内容
        fn content<'a>(&self, _post: &'a Post) -> &'a str {
            ""
        }
    }
    
    // 草稿
    struct Draft {}
    
    impl State for Draft {
        // 草稿状态 申请 之后就会进入 待审核状态
        fn apply_for_next_state(self: Box<Self>) -> Box<dyn State> {
            return Box::new(PendingReview {});
        }
    
        // 草稿状态 回退 之后还是 草稿状态
        fn reject_to_last_state(self: Box<Self>) -> Box<dyn State> {
            return self;
        }
    
        fn show(&self) {
            println!("Draft");
        }
    }
    
    // 待审核
    struct PendingReview {}
    
    impl State for PendingReview {
        // 待审核状态 申请 之后就会进入 已发布状态
        fn apply_for_next_state(self: Box<Self>) -> Box<dyn State> {
            return Box::new(Published {});
        }
    
        // 待审核状态 回退 之后就会进入 草稿状态
        fn reject_to_last_state(self: Box<Self>) -> Box<dyn State> {
            return Box::new(Draft {});
        }
    
        fn show(&self) {
            println!("PendingReview");
        }
    }
    
    // 已发布
    struct Published {}
    
    impl State for Published {
        // 已发布状态 申请 之后还是 已发布状态
        fn apply_for_next_state(self: Box<Self>) -> Box<dyn State> {
            return self;
        }
    
        // 已发布状态 回退 之后就会进入 待审核状态
        fn reject_to_last_state(self: Box<Self>) -> Box<dyn State> {
            return Box::new(PendingReview {});
        }
    
        fn show(&self) {
            println!("Published");
        }
    
        fn content<'a>(&self, post: &'a Post) -> &'a str {
            return &post.content;
        }
    }
    
    pub struct Post {
        state: Option<Box<dyn State>>,
        content: String,
    }
    
    impl Post {
        pub fn new() -> Post {
            Post {
                state: Some(Box::new(Draft {})),
                content: String::new(),
            }
        }
    
        /// 追加文章的内容
        pub fn add_content(&mut self, appending: &str) {
            self.content.push_str(appending);
        }
    
        /// 获取文章当前的内容
        pub fn content(&self) -> &str {
            return self.state.as_ref().unwrap().content(self);
        }
    
        /// 提交审核
        pub fn request_review(&mut self) {
            // take: 将 state 字段中的 Some 值取出并留下一个 None
            if let Some(s) = self.state.take() {
                self.state = Some(s.apply_for_next_state());
            }
        }
    
        /// 审核通过
        pub fn approve(&mut self) {
            if let Some(s) = self.state.take() {
                self.state = Some(s.apply_for_next_state());
            }
        }
    
        /// 查看文章当前的状态
        pub fn show_state(&self) {
            let state = self.state.as_ref();
            match state {
                Some(s) => {
                    print!("Now, the state of current post is: ");
                    s.show();
                }
                None => println!("Now, the state of current post is None."),
            };
        }
    }
    
    // #endregion
    
    // #region main.rs
    use blog::Post;
    
    fn main () {
        let mut post = Post::new();
    
        post.add_content("This is the first part.");
        post.show_state();
        println!("The content of post is: {}", post.content());
    
        post.request_review();
        post.show_state();
        println!("The content of post is: {}", post.content());
    
        post.approve();
        post.show_state();
        println!("The content of post is: {}", post.content());
    }
    // #endregion
    
    
  • 将上面通过 OOP 状态模式实现的代码,改为 Rust 类型

    // 来自官方教程的一个示例
    // #region lib.rs
    pub struct DraftPost {
        content: String,
    }
    
    impl DraftPost {
        pub fn add_content(&mut self, content: &str) {
            self.content.push_str(content);
        }
    
        pub fn request_review(self) -> PendingReviewPost {
            return PendingReviewPost {
                content: self.content,
            };
        }
    }
    
    pub struct PendingReviewPost {
        content: String,
    }
    
    impl PendingReviewPost {
        pub fn approve(self) -> Post {
            return Post {
                content: self.content,
            };
        }
    }
    
    pub struct Post {
        content: String,
    }
    
    impl Post {
        pub fn new() -> DraftPost {
            return DraftPost {
                content: String::new(),
            };
        }
    
        pub fn content(&self) -> &str {
            return &self.content;
        }
    }
    // #endregion
    
    // #region main.rs
    use blog::Post;
    
    fn main() {
        let mut post = Post::new();
        post.add_content("This is the first part of content.");
        // println!("The content of post is: {}", post.content); // 访问不到,因为 content 是私有的
        let post = post.request_review();
        // println!("The content of post is: {}", post.content); // 访问不到,因为 content 是私有的
        let post = post.approve();
        println!("The content of post is: {}", post.content());
    }
    // #endregion