Rust 基础知识22 - Rust面向对象编程

Rust的面向对象编程

  • Rust通过trait实现多态。
  • Rust没有继承的概念,但是作为替代方案可以使用Rust中默认的trait方法进行代码共享。
  • Rust是支持封装的,通过pub关键对外暴露结构体中的细节(假定结构体就是类)

知识汇总

模拟GUI绘图(假的)

  • 让所有在画布上的类实现 Draw trait 即可。
  • draw/src/lib.rs 这里定义了 Draw trait,并且定义了一个Screen 结构,并拥有一个 components 属性用来存放所有 Draw 的动态指针(因为无法事先确定Vec的元素数量)

pub trait Draw {
    fn draw(&self);
}

pub struct Screen {
    pub components: Vec>,
}

impl Screen {
    pub fn run (&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}
  • 然后定义组件,这里定义了一个Button、一个Option 建立文件 components/src/lib.rs 这两个结构都实现了 Draw
use draw::{Draw} ;

pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}


impl Draw for Button {
    fn draw(&self) {
        //
        println!("Draw button on ({},{}) | Label = {}", self.width, self.height, self.label);
    }
}

pub struct SelectBox {
    pub width: u32,
    pub height : u32,
    pub options: Vec,
}

impl Draw for SelectBox {
    fn draw(&self) {
        println!("Draw select-box on ({},{}) | Options : {:?}", self.width, self.height, self.options);
    }
}
  • chapter17/src/main.rs 主函数中,只要初始化Screen,最后调用 .run() 方法即可了,其实就是接口的传统用法。
use draw;
use components::{Button,SelectBox};
use draw::Screen;

fn main() {
    // 定义一个屏幕
    let screen = Screen {
        components: vec![
            Box::new(SelectBox{
                width: 75,
                height: 10,
                options: vec![
                    String::from("Yes"),
                    String::from("Maybe"),
                    String::from("No")
                ]
            }),
            Box::new(Button{
                width: 50,
                height: 10,
                label: "Ok".to_string()
            }),
        ]
    };
    screen.run();
}

trait 对象会执行动态派发

  • Rust编译器会在泛型使用trait约束是执行单态化,编译器会为每一个具体类型生成对应泛型函数和泛型方法的非泛型实现,并使用这些具体类型来替换泛型参数。

trait 对象必须保证对象安全

  • 如果一个trait中定义的所有方法满足下面两条规则,那么这个trait就是对象安全的

1、方法的返回类型不是Self。
2、方法中不包含任何泛型参数。

  • trait对象必须是安全的,因为Rust无法在使用trait对象时确定实现这个trait的具体类型究竟是什么,所以编译器无法在trait方法返回Self是使用原来的具体类型。
  • 标准库的Clone trait 就是一个不符合对象安全的列子:
pub trait Clone {
    fn clone(&self) -> Self;
}

实现一个简单的状态模式小实例

  • 模拟一篇博客文章可能拥有的各种行文,二期封装到不同的状态中,而Post 自身的方法对这些行文一无所知,通过这种代码的组织方式,我们只需要查看一个地方便能知晓已发布文章的行为差异。
  • 如果采用其他的实现来替代状态模式,那么就可能需要在Post甚至是main函数中使用match表达式来检查文章的状态,并根据状态执行不同的行为。
  • 而且用match在状态增加的时候还会导致代码的复杂度上升。
  • 基于状态模式可以免于在Post的方法或者使用Post的代码中添加match表达式。
  • 代码参考 首先是 main.rs:
// use draw;
// use components::{Button,SelectBox};
// use draw::Screen;
use blog::Post ;
fn main() {
    let mut post = OldPost::new();
    post.add_text("I ate a salad for lunch today.");
    assert_eq!("", post.content());
    println!("文章建立后的状态:{:?}", post.get_state());


    post.request_review();
    println!("提交审核的状态:{:?}", post.get_state());
    post.reject();
    println!("审核拒绝后的:{:?}", post.get_state());

    // 尝试修改文章,这里面修改应该是成功的,因为审核被拒绝后的状态是 Draft ,只有 Draft 才能修改。
    post.edit("See you at work!");
    println!("------");
    assert_eq!("", post.content(), "但是由于没有发布所以content() 还是无法返回内容的");

    post.request_review();
    println!("在审核:{:?}", post.get_state());

    // 继续修改,这是后状态式 PendingReview 所以修改会失败,那么内容理论上还是 "See you at work!"
    post.edit("I love you.");

    // 审核没有同意内容就拿不到
    assert_eq!("", post.content());

    // 通过
    post.approve();
    println!("同意后的状态:{:?}", post.get_state());
    assert_eq!("See you at work!", post.content());
}
  • 代码参考 blog/lib.rs
use std::fmt::{Debug, Formatter};

// use std::fmt::Display;

#[derive(Debug)]
pub struct Post {
    state: Option>,
    content: String,
}

impl Post {
    pub fn new () -> Post {
        Post {
            state: Some(Box::new(Draft{})),
            content: String::new(),
        }
    }
    pub fn get_state(&self) -> &Option>{
        &self.state
    }
    pub fn add_text(&mut self, text:&str) {
        self.content.push_str(text);
    }
    pub fn content(&self) -> &str {
        // 无聊的问题: ?为啥不直接 &self.content 这个问题很无语,如果这样就不叫状态模式了,状态模式中要把状态全部封装到State中
        // 这里面 unwrap() 方法不会返回Option的None,最终肯定返回T对象,也就是 State (接口)定义的返回值。
        // 通过 unwrap() 的过滤,那么实际上返回的就是 State (对象)了,调用状态上的content() 才可以哦。
        // 另外需要注意这里使用了 as_ref 这会得到一个 Option<&Box>
        self.state.as_ref().unwrap().content(self)
    }
    // 这个和接口 State::request_review 没有任何关系,参数定义也不一样,这就是个内部方法。
    pub fn request_review(&mut self) {
        // 题外话:为什么需要调用 self.state.take() 而不是其他的 ( *self.state,这样不行如果解引用生命周期就不对了)(self.state,这样不行不运行借用)。
        // 首先要了解一下 Option 的默认值 ,它实现了 Default 接口,也就是说Option 是有默认值的,默认值时None
        // take() 方法用来取出state 中Some 值的所有权,这样就可以踏踏实实的从Some(s)中取出,并在原来的位置留下None

        if let Some(s) = self.state.take() {
            // 更改state 的状态 s.request_review -> Box
            self.state = Some(s.request_review());
        }
    }
    pub fn approve(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.approve());
        }
    }
    pub fn reject(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.reject());
        }
    }
    pub fn edit( &mut self, new_content:&str) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.edit(self, new_content));
        }
    }
}

pub trait State {
    fn request_review(self:Box) -> Box;
    fn status_name(&self) -> String;
    fn approve(self:Box) -> Box;
    fn reject(self:Box) -> Box;
    // fn edit(&self, post:&mut Post,new_text:&str) {
    //
    // }
    fn edit(self:Box , post:&mut Post, new_str:&str) ->Box;

    // 这里涉及一个生命周期的概念要注意下,因为函数本身返回 post 参数的一部分
    // 所以要标明返回值 str 的生命周期,如下定义的生命周期长度与post 参数长度一致。
    fn content<'a> (&self, post:&'a Post) -> &'a str {
        // 这个默认的实现实际上隐藏了状态上要处理的 post.content 返回值。
        ""
    }
    // 如下方法没有定义生命周期所以会报错,可以放开试试,体会一下生命周期的作用。
    // fn content (&self, post:& Post) -> &str {
    //     // 这个默认的实现实际上隐藏了状态上要处理的 post.content 返回值。
    //     ""
    // }
}

// 给State 实现一个Debug 接口用于显示调试信息
impl Debug for dyn State {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("")
            .field(&"#########################".to_string())
            .field(&self.status_name())
            .field(&"#########################".to_string())
            .finish()
    }
}

#[derive(Debug)]
pub struct Draft {}

#[derive(Debug)]
pub struct PendingReview {}

#[derive(Debug)]
pub struct Published {}

impl State for Draft {
    // 注意这里的 self: Box 定义,而不是 self、&self、&mut self。
    // 这个语法意味着该方法只能被包裹着当前类型的Box实例调用,也就是上面 s = Box ,s.request_review()
    // ? 它会在调用过程中获取Box的所有权,并使旧的状态失效,从而将Post的状态值转换为一个新的状态。
    fn request_review(self: Box) -> Box {
        // 返回预览的状态
        Box::new(PendingReview{} )
    }

    fn status_name(&self) -> String {
        "Draft state".to_string()
    }

    fn approve(self: Box) -> Box {
        self
    }

    fn reject(self: Box) -> Box {
        self
    }

    fn edit(self:Box , post:&mut Post, new_str:&str) -> Box {
        post.content.clear();
        post.content.push_str(new_str);
        self
    }
}

impl State for PendingReview {
    // 对于一个已经处于审核状态的文章来说再次发起审批请求不会改变任何状态。
    fn request_review(self: Box) -> Box {
        self
    }

    fn status_name(&self) -> String {
        "PendingReview state".to_string()
    }

    fn approve(self: Box) -> Box {
        Box::new(Published {})
    }
    fn reject(self: Box) -> Box {
        Box::new(Draft {})
    }
    fn edit(self:Box , post:&mut Post, new_str:&str) -> Box {
        self
    }
}


impl State for Published {
    fn request_review(self: Box) -> Box {
        self
    }

    fn status_name(&self) -> String {
        "Published state".to_string()
    }

    fn approve(self: Box) -> Box {
        self
    }
    fn content<'a> (&self, post:&'a Post) -> &'a str {
        &post.content
    }
    fn reject(self: Box) -> Box {
        self
    }
    fn edit(self:Box , post:&mut Post, new_str:&str) -> Box {
        self
    }

    // // 没有生命周期的函数编译会报错,不信可以试试
    // fn content (&self, post:& Post) -> &str {
    //     // 这个默认的实现实际上隐藏了状态上要处理的 post.content 返回值。
    //     &post.content
    // }
}

上面状态模式小实例的改进

  • 上面的例子告诉我们Rust 可以做一些面向对象的模式,这没有问题,但是就上面例子的功能而言,其实有刚好的方法进行处理,我们可以将状态和行为编码编码成类型,这样更明了和简单,更重要的是,它不会产生很多无意义的空方法,比如 Post 类既然调用 .content() 返回的是空字符,那么不如让他根本没有 .content()
  • 参考下面的例子,首先是修改后的 main.rs
use blog2::{Post,PostDraft,PostReview};

/// 注意这里面的实现,很有指导意义,尤其对于我们理解Rust的设计和使用思路。
/// 我相信一旦对设计思路理解深入,那么对于使用上也就会明了,那么Rust 也会成为很顺手的工具。
fn main() {

    let mut blog = Post::new(); // 这会创建一个 PostDraft 也就是草稿
    blog.add_text("Hello rust, I love u.");
    let blog = blog.request_review();
    let blog = blog.approve();
    println!("--------------------------");
    println!("Blog publish success, content is : {}", blog.content());

    // 现在想编辑文章,但是 blog.edit() 并不存在,因为现在 blog:Post , 而 .edit() 是存在于 PostDraft 的。
    // 所以需要先进性状态转换调用 Post::unapprove() 然后  PostReview::reject() 最后调用 PostDraft::edit()
    let blog = blog.unapprove();
    let mut blog = blog.reject();
    blog.edit("So I will persevere learning it.");

    // 需要留意下面的写法是行不通的,(注释掉的这一行)
    // 因为Rust 在语义上表达式与变量是有很大不同的,这涉及到一些生命周期的问题。
    // let blog_content = blog.request_review().approve().content();
    let blog = blog.request_review();
    let blog = blog.approve();
    let blog_content = blog.content();
    println!("Blog edit success, content is : {}", blog_content);
}
  • 然后是我们的新类库(我没有删掉旧的,留个纪念)blog2/lib.rs


// 发布后的文章
pub struct Post {
    content: String,
}

// 文章草稿
pub struct PostDraft {
    content: String,
}

// 文章预览的状态
pub struct PostReview {
    content: String,
}

impl Post {
    // 新建立一个草稿
    pub fn new() -> PostDraft  {
        PostDraft {
            content : String::new(),
        }
    }

    // 获取文章内容
    pub fn content (& self) -> &String {
       &self.content
    }

    // 撤销文章的发布状态
    pub fn unapprove(self) -> PostReview {
        PostReview {
            content: self.content
        }
    }
}

impl PostDraft {

    // 返回一个预览状态的对象
    pub fn request_review(self) -> PostReview {
        PostReview {
            content : self.content
        }
    }

    // 向草稿添加文字
    pub fn add_text(&mut self, content:&str) {
        self.content.push_str(content);
    }

    // 编辑文本
    pub fn edit(&mut self, content:&str) {
        self.content.clear();
        self.add_text(content);
    }
}

impl PostReview {
    pub fn approve(self) -> Post {
        Post {
            content : self.content
        }
    }
    pub fn reject(self) -> PostDraft {
        PostDraft {
            content: self.content
        }
    }
}

结束

  • 感谢阅读,See you at work.

你可能感兴趣的:(Rust 基础知识22 - Rust面向对象编程)