Rust面向对象特性

文章目录

封装

rust 复制代码
pub struct AveragedCollection {
    // 结构体内部的成员缺省是私有的
    list: Vec<i32>,
    average: f64,
}

impl AveragedCollection {
    pub fn add(&mut self, value: i32) {
        self.list.push(value);
        self.update_average();
    }

    pub fn remove(&mut self) -> Option<i32> {
        let result = self.list.pop();
        match result {
            Some(value) => {
                self.update_average();
                Some(value)
            },
            None => None,
        }
    }

    pub fn average(&self) -> f64 {
        self.average
    }

    fn update_average(&mut self) {
        let total: i32 = self.list.iter().sum();
        self.average = total as f64 / self.list.len() as f64;
    }
}

基于特征对象vs基于泛型

rust 复制代码
pub trait Draw {
    fn draw(&self);
}

// 基于特征对象的实现,灵活的存储实现了某一个特征的类型的对象,有一些额外的开销
pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

// 基于泛型的实现,components存放的类型是同质的,没有额外的运行时的开销
pub struct Screen<T: Draw> {
    pub components: Vec<T>,
}

impl<T> Screen<T>
where
    T: Draw,
{
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

基于特征对象

lib.rs

rust 复制代码
pub trait Draw {
    fn draw(&self);
}

pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}



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

impl Draw for Button {
    fn draw(&self) {
        // 实际绘制按钮的代码
    }
}

main.rs

rust 复制代码
use gui::Draw;
use gui::{Button, Screen};
struct SelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        // code to actually draw a select box
    }
}
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: String::from("OK"),
            }),
        ],
    };

    screen.run();
}

静态派遣和动态派遣

在 Rust 中,静态派遣(Static Dispatch)和动态派遣(Dynamic Dispatch)是两种不同的调用方法,用于处理类型不确定的情况,尤其是在使用泛型和 trait 时。它们的主要区别在于如何确定函数调用时的具体类型,以及这一过程是在编译时还是运行时进行的。

静态派遣(Static Dispatch)

静态派遣是指在编译时就确定了函数调用的具体类型。Rust 会根据泛型的具体类型或 trait 对象的实现,生成直接调用相应方法的代码。所有这些决策都在编译时完成,因此效率较高。

如何实现:

  • 泛型(Generics):当你使用泛型时,Rust 会在编译时为每个使用的类型生成代码。
  • Trait Bound:通过在泛型中指定 trait 限定(比如 T: Trait),编译器可以为每种类型生成特定的代码,从而避免运行时的开销。
rust 复制代码
trait Speak {
    fn speak(&self);
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) {
        println!("Woof!");
    }
}

impl Speak for Cat {
    fn speak(&self) {
        println!("Meow!");
    }
}

fn greet<T: Speak>(animal: T) {
    animal.speak();
}

fn main() {
    let dog = Dog;
    let cat = Cat;
    
    greet(dog); // 静态派遣
    greet(cat); // 静态派遣
}

在这个例子中,greet 函数是泛型的,它接收任何实现了 Speak trait 的类型。Rust 会在编译时为 Dog 和 Cat 生成不同的代码,这就是静态派遣。

动态派遣(Dynamic Dispatch)

动态派遣是指函数调用的具体类型在运行时才决定。Rust 通过 trait 对象(即 dyn Trait)来实现动态派遣。当你使用 trait 对象时,Rust 会在运行时查找并调用合适的方法。

如何实现:

  • Trait 对象(Trait Object):使用 dyn Trait 来创建 trait 对象,这时会使用虚拟函数表(vtable)来实现方法的调用。这个 vtable 会在运行时生成,并根据实际类型查找合适的方法。
rust 复制代码
trait Speak {
    fn speak(&self);
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) {
        println!("Woof!");
    }
}

impl Speak for Cat {
    fn speak(&self) {
        println!("Meow!");
    }
}

fn greet(animal: &dyn Speak) {
    animal.speak();
}

fn main() {
    let dog = Dog;
    let cat = Cat;
    
    greet(&dog); // 动态派遣
    greet(&cat); // 动态派遣
}

在这个例子中,greet 函数接受一个 trait 对象 &dyn Speak。这意味着它在运行时会决定调用哪个类型的 speak 方法,而不是在编译时确定。

基于泛型

要基于泛型的方式改造代码,目标是避免使用 trait 对象(Box<dyn Draw>),而是直接使用泛型类型来提供类型安全和更高效的静态派遣。

通过这种方式,Screen 的 components 不再需要使用 Box<dyn Draw>,而是直接使用泛型参数 T,这样可以在编译时确定具体类型,避免了动态分发的开销。

lib.rs

rust 复制代码
// lib.rs

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

pub struct Screen<T: Draw> {
    pub components: Vec<T>,
}

impl<T: Draw> Screen<T> {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

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

impl Draw for Button {
    fn draw(&self) {
        // 实际绘制按钮的代码
        println!("Drawing Button: {}", self.label);
    }
}

main.rs

rust 复制代码
// main.rs
use gui::{Button, Draw, Screen};

struct SelectBox {
    width: u32,
    height: u32,
    options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        // 实际绘制选择框的代码
        println!("Drawing SelectBox with options: {:?}", self.options);
    }
}

fn main() {
    // 只能放一种类型
    let screen = Screen {
        components: vec![SelectBox {
            width: 75,
            height: 10,
            options: vec![
                String::from("Yes"),
                String::from("Maybe"),
                String::from("No"),
            ],
        }],
    };
    screen.run();
}

状态设计模式

状态模式(state pattern)是一个面向对象设计模式。该模式的关键在于一个值有某些内部状态,体现为一系列的 状态对象,同时值的行为随着其内部状态而改变。状态对象共享功能:当然,在 Rust 中使用结构体和 trait 而不是对象和继承。每一个状态对象负责其自身的行为,以及该状态何时应当转移至另一个状态。持有一个状态对象的值对于不同状态的行为以及何时状态转移毫不知情。

使用状态模式意味着当程序的业务需求改变时,无需修改保存状态值的代码或使用值的代码。

我们只需更新某个状态对象内部的代码,即可改变其规则,也可以增加更多的状态对象。

面向对象的思想

lib.rs

rust 复制代码
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_text(&mut self, text: &str) {
        self.content.push_str(text);
    }

    pub fn content(&self) -> &str {
        // as_ref()获取的是Option<&Box<dyn State>>
        // unwrap()返回的是&Box<dyn State>
        // 由于dref的自动转型,所以可以直接调用content()方法
        self.state.as_ref().unwrap().content(self)
    }
    pub fn request_review(&mut self) {
        // self.state.take取出状态(移走value)结构体中state还剩下None成员
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review()) //获取一个新的状态
        }
    }
    pub fn approve(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.approve())
        }
    }
}

trait State {
    // 获取Self的所有权,返回State的特征对象
    fn request_review(self: Box<Self>) -> Box<dyn State>;
    fn approve(self: Box<Self>) -> Box<dyn State>;
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        ""
    }
}

struct Draft {}

impl State for Draft {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        Box::new(PendingReview {})
    }
    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }
}

struct PendingReview {}

impl State for PendingReview {
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        self
    }
    fn approve(self: Box<Self>) -> Box<dyn State> {
        Box::new(Published {})
    }
}

struct Published {}

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

    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }

    // 入参有多个引用,输出参数也是引用,需要标注生命周期注解
    fn content<'a>(&self, post: &'a Post) -> &'a str {
        &post.content
    }
}

main.rs

rust 复制代码
use blog::Post;

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

    post.add_text("I ate a salad for lunch today");
    assert_eq!("", post.content());

    post.request_review();
    assert_eq!("", post.content());

    post.approve();
    assert_eq!("I ate a salad for lunch today", post.content());
}

编译

bash 复制代码
 cargo run
   Compiling blog v0.1.0 (/home/wangji/installer/rust/bobo/smartPtr)
warning: unused variable: `post`
  --> src/lib.rs:41:27
   |
41 |     fn content<'a>(&self, post: &'a Post) -> &'a str {
   |                           ^^^^ help: if this is intentional, prefix it with an underscore: `_post`
   |
   = note: `#[warn(unused_variables)]` on by default

warning: `blog` (lib) generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 15.87s
     Running `target/debug/blog`

rust思想:将状态和行为编码为类型(将状态编码成类型系统)

对状态类型的编译直接移动到了结构体中了

lib.rs

rust 复制代码
pub struct Post {
    content: String,
}

pub struct DraftPost {
    content: String,
}

impl Post {
    pub fn new() -> DraftPost {
        DraftPost {
            content: String::new(),
        }
    }

    pub fn content(&self) -> &str {
        &self.content
    }
}

impl DraftPost {
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }

    pub fn request_review(self) -> PendingReviewPost {
        PendingReviewPost {
            content: self.content,
        }
    }
}

pub struct PendingReviewPost {
    content: String,
}

impl PendingReviewPost {
    pub fn approve(self) -> Post {
        Post {
            content: self.content,
        }
    }
}

main.rs

rust 复制代码
use blog::Post;

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

    post.add_text("I ate a salad for lunch today");

    let post = post.request_review();

    let post = post.approve();

    assert_eq!("I ate a salad for lunch today", post.content());
}

pub fn approve(self) 和 pub fn approve(&self)区别

pub fn approve(self) 的含义

  • 所有权转移:当函数签名中使用 self 时,意味着该方法获得了 self 的所有权。这意味着 self 的所有权被移动到函数内部,并且调用该方法后,原来的对象将无法再被使用。

  • 适用场景:当你希望在方法中消费 self,即你不再需要继续使用调用者实例时,通常会使用 self。例如,你可能会对对象进行销毁、修改或替换,或者将其传递给其他地方。

rust 复制代码
struct User {
    name: String,
}

impl User {
    pub fn approve(self) {
        println!("Approving user: {}", self.name);
        // 此时 `self` 的所有权已被转移,外部无法再使用 `User` 实例
    }
}

fn main() {
    let user = User { name: String::from("Alice") };
    user.approve(); // 这里 `user` 的所有权被转移给了 `approve` 方法
    // println!("{}", user.name); // 这将无法编译,因为 `user` 的所有权已被转移
}

pub fn approve(&self) 的含义

借用:当函数签名中使用 &self 时,表示方法是对 self 的 借用。这意味着方法不会获取对象的所有权,它只会借用 self,允许你在方法内部使用对象,但调用方法后,self 仍然可用。

适用场景:当你只需要读取 self 中的数据,或者只是对对象进行某些操作而不修改或销毁它时,使用 &self。借用可以保证你不会错误地修改原对象或消耗掉它。

rust 复制代码
struct User {
    name: String,
}

impl User {
    pub fn approve(&self) {
        println!("Approving user: {}", self.name);
        // 这里仅借用了 `self`,所以 `self` 的所有权没有被转移
    }
}

fn main() {
    let user = User { name: String::from("Alice") };
    user.approve(); // 借用 `user` 并调用方法
    println!("{}", user.name); // 仍然可以继续使用 `user`
}

参考

相关推荐
码上淘金4 小时前
【Python】Python常用控制结构详解:条件判断、遍历与循环控制
开发语言·python
Brilliant Nemo4 小时前
四、SpringMVC实战:构建高效表述层框架
开发语言·python
格林威6 小时前
Baumer工业相机堡盟工业相机的工业视觉中为什么偏爱“黑白相机”
开发语言·c++·人工智能·数码相机·计算机视觉
橙子199110166 小时前
在 Kotlin 中什么是委托属性,简要说说其使用场景和原理
android·开发语言·kotlin
androidwork6 小时前
Kotlin Android LeakCanary内存泄漏检测实战
android·开发语言·kotlin
学地理的小胖砸6 小时前
【Python 基础语法】
开发语言·python
IT专业服务商7 小时前
联想 SR550 服务器,配置 RAID 5教程!
运维·服务器·windows·microsoft·硬件架构
海尔辛7 小时前
学习黑客5 分钟小白弄懂Windows Desktop GUI
windows·学习
gushansanren7 小时前
基于WSL用MSVC编译ffmpeg7.1
windows·ffmpeg
DanB248 小时前
Java笔记4
java·开发语言·笔记