Rust面向对象特性

server/2024/11/17 3:27:07/

文章目录

  • 封装
  • 基于特征对象vs基于泛型
    • 基于特征对象
    • 静态派遣和动态派遣
      • 静态派遣(Static Dispatch)
      • 动态派遣(Dynamic Dispatch)
    • 基于泛型
  • 状态设计模式
    • 面向对象的思想
    • rust思想:将状态和行为编码为类型(将状态编码成类型系统)
      • pub fn approve(self) 和 pub fn approve(&self)区别
      • pub fn approve(&self) 的含义
  • 参考

封装

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>
whereT: 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.rspub 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());
}

编译

 cargo runCompiling 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 defaultwarning: `blog` (lib) generated 1 warningFinished `dev` profile [unoptimized + debuginfo] target(s) in 15.87sRunning `target/debug/blog`

rust_479">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`
}

参考

  • 第17章~Rust和面向对象特性

  • Rust 的面向对象特性

  • 面向对象设计模式的实现


http://www.ppmy.cn/server/142547.html

相关文章

leetcode417. Pacific Atlantic Water Flow

Pacific Atlantic Water Flow There is an m x n rectangular island that borders both the Pacific Ocean and Atlantic Ocean. The Pacific Ocean touches the island’s left and top edges, and the Atlantic Ocean touches the island’s right and bottom edges. The …

Spring Boot编程训练系统:最佳实践与技巧

3系统分析 3.1可行性分析 通过对本编程训练系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本编程训练系统采用SSM框架&#xff0c;JAVA作为开发语言&#…

windows二进制安全零基础(二)

文章目录 栈&#xff08;The Stack&#xff09;调用约定&#xff08;Calling Conventions&#xff09;函数返回机制 在x86架构中&#xff0c;栈&#xff08;Stack&#xff09;是一个非常重要的内存区域&#xff0c;它用于支持线程的短期数据需求&#xff0c;如函数调用、局部变…

nacos集群源码解析-cp架构

目录 1 简介 1.1 什么是 CP 架构&#xff1f; 1.2 Nacos 中的 CP 架构特点 1.3 优缺点 1.4适用场景 2 cp架构的主节点选举 2.1 选举流程 2.2 总结 3 cp架构主节点的心跳发送 3.1 leader发送心跳 3.2 follower接收心跳 3.3 总结 4 cp架构的服务注册 4.1 注册流程 …

Kettle配置数据源错误“Driver class ‘org.gjt.mm.mysql.Driver‘ could not be found”解决记录

问题描述 错误提示&#xff1a;“Driver class ‘org.gjt.mm.mysql.Driver’ could not be found, make sure the ‘MySQL’ driver (jar file) is installed.” 原因分析&#xff1a; 根据错误提示是缺少了相关的数据源连接jar包。 解决方案&#xff1a; 安装对应的Mysql…

关于element-plus中el-table组件宽度一直连续不断增加的问题

问题出现 原因 //基本还原了 使用场景 原因就是flex:1导致的el-table 不断的渲染宽度<div style"display:flex;"><div style"width:200px"></div><div style"flex:1"><el-table></el-table></div> &…

Python 正则表达式进阶用法:边界匹配

Python 正则表达式进阶用法&#xff1a;边界匹配 正则表达式是一种强大的工具&#xff0c;用于处理文本中的模式匹配。它广泛应用于文本查找、替换、数据清洗等任务。在学习了正则表达式的基础知识后&#xff0c;掌握更高级的用法将使得正则表达式的应用更加灵活。边界匹配&am…

初识Linux · 信号产生

目录 前言&#xff1a; 预备知识 信号产生 前言&#xff1a; 前文已经将进程间通信介绍完了&#xff0c;介绍了相关的的通信方式。在本文介绍的是信号部分&#xff0c;那么一定有人会有问题是&#xff1a;信号和信号量之间的关系是什么呢&#xff1f;答案是&#xff0c;它们…