rust学习-面向对象

面向对象编程(Object-Oriented Programming,OOP)

封装细节

main.rs

use rust_demo::AveragedCollection;fn main() {let mut ac = AveragedCollection::new();println!("ac={:?}", ac);ac.add(3);ac.add(5);ac.add(7);println!("ac={:?}", ac);
}

lib.rs

#[derive(Debug)]// 结构体公有
pub struct AveragedCollection {// 里面的内容私有list: Vec<i32>,average: f64,
}impl AveragedCollection {// 方法公有pub fn new() -> Self {AveragedCollection{list:Vec::new(),average:0.0,}}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;}
}

继承

如果一个语言必须有继承才能被称为面向对象语言的话,那么 Rust 就不是面向对象的。无法定义一个结构体继承父结构体的成员和方法
Rust 也提供了其他的解决方案

选择继承有两个主要的原因:
(1)重用代码:一旦为一个类型实现了特定行为,继承可以对一个不同的类型重用这个实现,Rust 代码可以使用默认 trait 方法实现来进行共享
(2)使用继承的原因与类型系统有关:子类型可以用于父类型被使用的地方,多态(polymorphism)

rust使用 trait 对象而不是继承
近来继承作为一种语言设计的解决方案在很多语言中失宠:共享多于所需的代码风险,子类不应总是共享其父类的所有特征

示例背景

以GUI库接口为例:通过遍历列表并调用每一个项目的 draw 方法来将其绘制到屏幕上
在拥有继承的语言中,可以定义一个名为 Component 的类,该类上有一个 draw 方法。其他的类比如 Button、Image 和 SelectBox 会从 Component 派生并因此继承 draw 方法。它们各自都可以覆盖 draw 方法来定义自己的行为

Rust的实现方式:
定义一个 Draw trait,其中包含名为 draw 的方法。
定义一个存放 trait 对象(trait object) 的 vector。
trait 对象指向一个实现了指定 trait 的类型的实例,以及一个用于在运行时查找该类型的 trait 方法的表

库实现

trait 对象不同于传统的对象,因为不能向 trait 对象增加数据
trait 对象并不像其他语言中的对象那么通用:
其(trait 对象)具体的作用是允许对通用行为进行抽象

pub trait Draw {fn draw(&self);
}pub struct Screen {//  vector 的类型是 Box<dyn Draw>,为一个 trait 对象// 它是 Box 中任何实现了 Draw trait 的类型的替身pub components: Vec<Box<dyn Draw>>,
}impl Screen {pub fn run(&self) {// 模拟GUI的渲染for component in self.components.iter() {component.draw();}}
}// 通用库中实现具体结构体和对应的Draw Trait
pub struct Button {pub width: u32,pub height: u32,pub label: String,
}impl Draw for Button {fn draw(&self) {// 实际绘制按钮的代码}
}

main实现

main中可以增加其他需要参与渲染的特制化的结构体

use gui::Draw;
use gui::{Screen, Button};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();
}

Screen 实例必须拥有一个全是 Button 类型或者全是TextField 类型的组件列表

和泛型类型参数的区别

泛型类型参数一次只能替代一个具体类型,如果只需要同质(相同类型)集合,则倾向于使用泛型和 trait bound,其定义会在编译时采用具体类型进行单态化,即静态分发
trait 对象则允许在运行时替代多种具体类型,当使用 trait 对象时,Rust 必须使用动态分发
动态分发可以通过牺牲少量运行时性能来为你的代码提供一些灵活性

如下示例只能渲染vec{小猫1,小猫2,…},而不能渲染vec{小猫1,小狗2,…}

pub trait Draw {fn draw(&self);
}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();}}
}

Trait 对象要求对象安全

如果一个 trait 中所有的方法有如下属性时,则该 trait 是对象安全的:
(1)返回值类型不为 Self
(2)方法没有任何泛型类型参数

不是对象安全的例子:Clone trait

pub trait Clone {fn clone(&self) -> Self;
}

在 String 实例上调用 clone 方法时会得到一个 String 实例
当调用 Vec 实例的 clone 方法会得到一个 Vec 实例

可以理解为:trait对象需要Self,但是如果某个trait返回Selft,它可以修改泛型参数的类型/trait对象所指对象的方法等,导致trait对象无法用

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

面向对象编程

一个增量式的发布博文的工作流
(1)博文从空白的草案开始。
(2)一旦草案完成,请求审核博文。
(3)一旦博文过审,它将被发表。
(4)只有被发表的博文的内容会被打印

状态模式

// cat main.rs
use rust_demo::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());
}
// cat lib.rs
// Post 的方法并不知道这些不同类型的行为:Draft、PendingReview 和 Published
pub struct Post {// state 字段是私有的state: Option<Box<dyn State>>,content: String,
}impl Post {pub fn new() -> Post {Post {// 博文初始状态为草案state: Some(Box::new(Draft {})),content: String::new(),}}// 获取一个 self 的可变引用,通过该方法改变Post实例pub fn add_text(&mut self, text: &str) {self.content.push_str(text);}// 请求审核pub fn request_review(&mut self) {// 调用 take 方法将 state 字段中的 Some 值取出并留下一个 None// Rust 不允许结构体实例中存在值为空的字段,所以才要用Option<Box<dyn State>>类型if let Some(s) = self.state.take() {self.state = Some(s.request_review())}}// 审核通过// 将 state 设置为审核通过时应处于的状态pub fn approve(&mut self) {if let Some(s) = self.state.take() {self.state = Some(s.approve())}}// 读取文本接口pub fn content(&self) -> &str {// as_ref():需要 Option 中值的引用而不是获取其所有权// state 是一个 Option<Box<State>>,调用 as_ref 会返回一个 Option<&Box<State>>// unwrap,这里永远也不会 panic,状态图确保它返回时均是一个Some值// 当调用其 content 时,解引用强制转换会作用于 & 和 Box// 这里原来调用的是trait中的content方法self.state.as_ref().unwrap().content(self)// 改用下面方式实现// 	self类型:&rust_demo::Post// self.state类型:core::option::Option<alloc::boxed::Box<dyn rust_demo::State>>// curStatRef的类型:core::option::Option<&alloc::boxed::Box<dyn rust_demo::State>>// let curStatRef = self.state.as_ref();// innerWrap的类型:&alloc::boxed::Box<dyn rust_demo::State>// let innerWrap = curStatRef.unwrap();// info类型:&str// let info = innerWrap.content(self);// info}
}// State trait 定义了所有不同状态的博文所共享的行为
trait 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 {// 传入进来的post的类型是&rust_demo::Post""}
}// Draft、PendingReview 和 Published 状态都会实现 State 状态
// 无论 state 是何值,Post 的 request_review 方法都是一样的。每个状态只负责它自己的规则
struct Draft {}impl State for Draft {// 状态流转// 该方法只可在持有这个类型的 Box 上被调用// 获取了 Box<Self> 的所有权使老状态无效化// 将 state 的值移出 Post 而不是借用它// 要将 state 临时设置为 None 来获取 state 值// 而不是使用 self.state = self.state.request_review()// 确保了当 Post 被转换为新状态后不能再使用老 state 值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 {// 状态流转// 该方法只可在持有这个类型的 Box 上被调用fn request_review(self: Box<Self>) -> Box<dyn State> {Box::new(Published {})}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}// 获取 post 的引用作为参数,并返回 post 一部分的引用// 所以返回的引用的生命周期与 post 参数相关fn content<'a>(&self, post: &'a Post) -> &'a str {// 传入进来的post的类型是&rust_demo::Post&post.content}
}

缺点:
(1)状态实现了状态之间的转换,一些状态会相互联系:
如果在 PendingReview 和 Published 之间增加另一个状态,比如 Scheduled,
则不得不修改 PendingReview 中的代码来转移到 Scheduled
(2)重复的逻辑:
不同状态之间都需要实现trait的所有接口
Post 中 request_review 和 approve 这两个类似的实现。都委托调用了 state 字段中 Option 值的同一方法

解决办法

草案博文在可以发布之前必须被审核通过。
等待审核状态的博文应该仍然不会显示任何内容

// cat main.rs
use rust_demo::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());
}
// cat lib.rs
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);}// request_review 获取 self 的所有权,消费 DraftPost// 转换为 PendingReviewPost// 这样在调用 request_review 之后就不会遗留任何 DraftPost 实例pub fn request_review(self) -> PendingReviewPost {PendingReviewPost {content: self.content,}}
}pub struct PendingReviewPost {content: String,
}impl PendingReviewPost {// approve 获取 self 的所有权,消费 PendingReviewPost// 转换为 Post// 这样在调用 approve 之后就不会遗留任何 PendingReviewPost 实例pub fn approve(self) -> Post {Post {content: self.content,}}
}// Post 的 new -> DraftPost
// DraftPost 的 request_review -> PendingReviewPost
// PendingReviewPost 的approve -> Post
// 最终只需要Post打印即可

修改 main 来重新赋值 post 使得这个实现不再完全遵守面向对象的状态模式:
状态间的转换不再完全封装在 Post 实现中
得益于类型系统和编译时类型检查,得到无效状态是不可能的
上述的取舍真的牛逼!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/8929.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Vue如何配置eslint

eslint官网: eslint.bootcss.com eslicate如何配置 1、选择新的配置&#xff1a; 2、选择三个必选项 3、再选择Css预处理器 4、之后选择处理器 5、选择是提交的时候就进行保存模式 6、放到独立的配置文件上去 7、最后一句是将自己的数据存为预设 8、配合console不要出现的规则…

【Java中的Thread线程的简单方法介绍和使用详细分析】

文章目录 前言一、run() 和 start() 方法二、sleep() 方法三、join() 方法总结 前言 提示&#xff1a;若对Thread没有基本的了解&#xff0c;可以先阅读以下文章&#xff0c;同时部分的方法已经在如下两篇文章中介绍过了&#xff0c;本文不再重复介绍&#xff01;&#xff01;…

Java: Commons-io

Commons-io是apache开源基金组织提供的一组有关IO操作的开源工具包。 1.作用 提高IO流的开发效率。 2.FileUtils类&#xff08;文件夹/文件&#xff09; 1.常见方法 static void copyFile(File srcFile&#xff0c;File destFile): 复制文件static void copyDirectory(Fil…

【Luogu】 P3665 [USACO17OPEN] Switch Grass P

题目链接 点击打开链接 题目解法 首先给出 2 个结论&#xff1a; 最接近的不同颜色的点一定是相邻的点 证明&#xff1a;假设最接近的不同颜色的点 ( u , v ) (u,v) (u,v) 不相邻&#xff0c;那么 u , v u,v u,v 之间的路径中必有相邻的不同颜色点 ( u ′ , v ′ ) (u,v…

在nginx上部署nuxt项目

先安装Node.js 我安的18.17.0。 安装完成后&#xff0c;可以使用cmd&#xff0c;winr然cmd进入&#xff0c;测试是否安装成功。安装在哪个盘都可以测试。 测试 输入node -v 和 npm -v&#xff0c;&#xff08;中间有空格&#xff09;出现下图版本提示就是完成了NodeJS的安装…

【js Call、apply与bind 区别】

JavaScript 中的 Call、apply 和 bind 都是用来改变函数的执行上下文&#xff08;即 this 指向&#xff09;的方法&#xff0c;但它们之间有些区别&#xff1a; Call 和 apply Call 和 apply 都是 Function.prototype 的方法。它们的作用是改变函数的 this 指向&#xff0c;并…

electron 生成 arm64 的包

vue electron&#xff1a;https://blog.csdn.net/qq1195566313/article/details/131713875 打包配置修改如下 electronBuilder.build({config: {appId: com.example.app,productName: vite-electron,directories: {output: path.join(process.cwd(), "release"), …

Windows OS CMD 常用工具 の 命令合集

# First Of All 每次想要修改环境变量都要按部就班点开系统属性、高级系统设置、环境变量。这种操作实在是太繁琐了&#xff0c;对于我一个懒人来讲实在是 忍无可忍 。如果可以使用 WINR 或 CMD 直接打开系统内的一些工具&#xff0c;是不是就可以节省很多时间&#xff1b;是不…

【node.js】01-fs读写文件内容

目录 一、fs.readFile() 读取文件内容 二、fs.writeFile() 向指定的文件中写入内容 案例&#xff1a;整理txt 需求&#xff1a; 代码&#xff1a; 一、fs.readFile() 读取文件内容 代码&#xff1a; //导入fs模块&#xff0c;从来操作文件 const fs require(fs)// 2.调…

Elasticsearch查询裁剪

如果source有成千上百个字段,查询的数据没法看 某些敏感字段不能随意展示 响应数据较大影响网络带宽 查看文档信息 查看ffbf索引id为123的文档信息 GET /ffbf/_doc/123返回结果 {"_index" : "ffbf","_type" : "_doc","_id&qu…

【雕爷学编程】Arduino动手做(173)---SG90舵机双轴云台模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

windows配置anaconda环境变量

windows 配置 anaconda 环境变量&#xff0c;可以做到 cmd 中调用 conda 命令&#xff0c;不必每次都去找 Anaconda Prompt 文章目录 1. 找到Anaconda的安装位置2. 配置系统环境变量2.1 一步到位2.1 或者手动打开2.2 配置环境变量 3. 检查 1. 找到Anaconda的安装位置 默认安…

kafka面试题

kafka基本概念 Producer 生产者&#xff1a;负责将消息发送到 BrokerConsumer 消费者&#xff1a;从 Broker 接收消息Consumer Group 消费者组&#xff1a;由多个 Consumer 组成。消费者组内每个消费者负责消费不同分区的数据&#xff0c;一个分区只能由一个组内消费者消费&am…

C++[第十七章]--模板引入

模板引入 文章目录 模板引入1、函数模板格式2、使用3、参数推导过程有限的类型转换苛刻的类型匹配推导示例1、函数模板 建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来表达。这个通用函数就称为函数模板 格式 template<类型参数表> 返回值 函…

Vue中TodoList案例_动画

MyItem.vue : 主要是引入了import animate.css样式库&#xff0c;animate.css样式库配置见上一篇文章animate.css样式库&#xff0c;然后再li标签外套了transition标签&#xff0c;引用了name里面的名称是animate.css拿过来的&#xff0c;绑定了enter-active-class和leave-act…

电脑硬盘指的是什么?电脑硬盘长什么样子呢

在很早之前就听说过电脑里面有硬盘&#xff0c;但是不知道电脑硬盘是什么样子&#xff0c;本章文章结合硬盘的接口类型&#xff0c;以及应用技术&#xff0c;说说与硬盘样式有关的知识 一。机械硬盘 如果从硬盘的应用技术来区分硬盘&#xff0c;一般分为两种&#xff0c;早些年…

2023年第三届能源、电力与电气工程国际会议 (CoEEPE 2023)

会议简介 Brief Introduction 2023年第三届能源、电力与电气工程国际会议(CoEEPE 2023) 会议时间&#xff1a;2023年11月22日-24日 召开地点&#xff1a;澳大利亚墨尔本 大会官网&#xff1a;www.coeepe.org 2023年第三届能源、电力与电气工程国际会议(CoEEPE 2023)由安徽大学、…

【大数据运维-ambari】自定义fair-scheduler.xml配置文件导致ambari-server启动失败

将自定义fair-scheduler.xml放到 /var/lib/ambari-server/resources/stacks/HDP/3.0/services/YARN/configuration目录下&#xff0c;重启ambari-server失败&#xff1a; 日志显示&#xff1a; 进ambari数据库查看发现数据应该是对的。 删掉之前自定义的文件fair-scheduler.x…

JavaScript function默认参数赋值前后顺序差异

1、(num1,num2num1) 当传值仅传一个参数时&#xff0c;先给到第一个参数即num1&#xff0c;num1再赋值给num2&#xff0c; function sum(num1, num2 num1) {console.log(num1 num2) } sum(10)//20 sum(10,3)//13 2、(t2t1,t1) 当传值仅有一个参数时&#xff0c;先给到第一个…

qt signal slots lambda

这里用到了qt的版本检测 连接 Combox的currentIndexChanged事件 emit来触发处理的事件 &#xff0c;进行业务或逻辑处理 这样的写法是lambda表达式的写法&#xff0c;和c#中的 (obj)>{ //todo } 类同 [](int indx){ //todo } #if QT_VERSION > QT_VERSION_CHECK(5,7,0)c…