【Rust自学】17.3. 实现面向对象的设计模式
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
17.3.1. 状态模式
状态模式(state pattern) 是一种面向对象设计模式,指的是一个值拥有的内部状态由数个状态对象(state object) 表达而成,而值的行为随着内部状态的改变而改变。
使用状态模式意味着:业务需求变化时,不需要修改持有状态的值的代码,或者是使用这个值的代码;只需要更新状态对象内部的代码,以改变其规则,或者是增加一些新的状态对象。
看个例子:
博客文章一开始是一个空草稿。草稿完成后,要求对该帖子进行审查。当帖子获得批准后,就会发布。只有已发布的博客帖子才会返回要打印的内容,因此不会意外发布未经批准的帖子。
main.rs
:
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());
}
- 使用
Post::new
创建新的博客文章草稿。首先创建一个Post
类型的实例,命名为post
。它是可变的,因为处于草稿状态的文章还可以修改 - 然后通过
Post
上的add_text
方法增加了"I ate a salad for lunch today"这句话 - 接下来使用
request_review
方法请求审批 - 最后使用
approve
方法获得审批通过
PS:添加的assert_eq!
在代码中用于演示目的。单元测试可能包含断言草稿博客文章从content
方法返回一个空字符串,但我们不打算为此示例编写测试。
lib.rs
:
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 {""}pub fn request_review(&mut self) { 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 {fn request_review(self: Box<Self>) -> Box<dyn State>;fn approve(self: Box<Self>) -> Box<dyn State>;
}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> { Box::new(Published {}) }
}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 }
}
-
Post
结构体有两个字段,一个字段是state
,用于存储文章当下的状态,它一共有三种状态:草稿、等待审批和已发布。Box<dyn State>
代表只要是实现了State
trait的类型就可以存入
通过这个字段,Post
类型能在内部管理状态与状态之间的变化,这个状态的变化是通过用户调用Post
上的方法实现的,而用户只能通过调用这些方法来改变值(因为Post
下的字段未设为公开,所以用户没办法直接修改字段的值)。 -
下文通过
impl
块为Post
实现了一些方法:-
new
函数用于创建一个Post
类型的实例,其初始的content
值是一个空的字符串;初始的state
处于草稿状态,所以state
存储的是Draft
结构体(下文有讲) -
add_text
会往content
字段使用pusth_str
方法来添加内容 -
即使我们调用了
add_text
并向帖子添加了一些内容,我们仍然希望content
方法返回一个空字符串切片,因为帖子仍处于草稿状态。 -
request_review
会提取出state
字段下的状态,取出来之后,State
就会暂时变为None
,因为所有权被移动出来了。这个时候调用state
上的request_review
方法来请求审批。
当state
是Draft
状态时,就会调用Draft
结构体上的request_review
方法(下文有讲),把state
字段的值从Draft
变为了PendingReview
,把状态更新回state
上。
-
-
approve
表示审批通过,其写法跟request_review
差不多,把状态取出来,调用self
上的approve
方法来更新状态。 -
State
trait目前定义了两个方法,只有签名,没有具体实现:request_review
表示请求审批approve
表示审批通过
PS:注意它的签名的参数是Box<self>
,与self
和mut self
有区别,Box<self>
意味着它只能被包裹着当前类型的Box
实例,它会在调用过程中获取Box(self)
的所有权,并使旧的实效,从而修改状态。
-
Draft
用于表示草稿状态,不需要实际的内容,所以只要声明一个没有字段的结构体即可 -
通过
impl
块为Draft
实现了State
trait:request_review
表示请求审批,把值变为了PendingReview
。approve
表示审批通过。由于approve
在此时没用,只需要把本身传回去即可,所以返回值是self
。
-
PendingReviewing
用于表示等待审批,不需要实际的内容,所以只要声明一个没有字段的结构体即可 -
通过
impl
块为PendingReview
实现了State
trait:request_review
表示请求审批,此时状态不会变,只需要把本身传回去即可,所以返回值是self
。approve
表示审批通过,返回Published
结构体。
-
Published
用于表示已发表,不需要实际的内容,所以只要声明一个没有字段的结构体即可 -
通过
impl
块为Published
实现了State
trait。但是它都处于已发布的状态了,所以request_review
和approve
都没啥用,直接返回本身self
就行。
我们为什么不使用枚举类型的变体作为帖子状态?这当然是一个可能的解决方案,但它的其缺点之一是使用枚举是每个检查枚举值的地方都需要一个match
表达式或类似的表达式来处理每个可能的变体。
这样写会存在很多重复的代码,有些代码根本没用;但是它的优点也很明显:无论状态值是什么Post
上的request_review
方法都不需要改变,每个状态都负责自己的运行规则。
这里还有content
方法还需要修改,我们想要在发布状态下使它可见,而其他两种情况下看不到。一样可以使用面向对象的设计模式。以下是原来的代码:
pub fn content(&self) -> &str {""
}
首先在State
trait下定义content
方法:
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
的引用,然后返回的可能是Post
中某一部分的引用,所以返回值的生命周期和Post
参数的生命周期是相关联的。
对于Draft
和PendingReview
来说默认实现就可以满足需求了。只需要在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}
}
最后修改Post
上的content
方法:
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 {self.state.as_ref().unwrap().content(&self)}pub fn request_review(&mut self) { 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()) } }
}
我们需要先看Option
里面值的引用,所以说调用了as_ref
方法得到Option<&T>
,为了解包必须写一步错误处理,用unwrap
即可。最后就调用content
方法,根据所处的状态不同,content
的具体实现也会有所不同。
17.3.2. 状态模式的取舍权衡
状态模式的优点如上所见:无论状态值是什么Post
上的request_review
方法都不需要改变,每个状态都负责自己的运行规则。
但它的缺点也比较明显:
- 需要重复实现一些逻辑代码
- 某些状态之间是相互耦合的,如果我们新增一个状态,这时候跟它相关联的代码就需要修改
17.3.3. 将状态和行为编码为类型
如果我们严格按照面向对象的模式写当然是可行的,但是发挥不出Rust的全部威力。
下面我们会结合Rust的特点来修改,具体来说就是把状态和行为改为具体的类型。Rust类型检查系统会通过编译时错误来阻止用户使用无效的状态。
修改后的代码如下:
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);}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,}}
}
-
声明了
Post
和DraftPost
两个结构体,这两者都有一个存储String
类型的content
字段 -
通过
impl
块写了Post
的new
方法和content
方法:new
方法会创建一个空的DraftPost
结构体content
方法就会返回本身的content
字段的值
-
通过
impl
块写了DraftPost
的方法:add_text
方法用于给DraftPost
的content
添加文字request_review
方法用于请求审批,调用这个方法就会返回另一个状态PendingReviewPost
,表示正在审批中。这个状态是在下文定义的
-
声明了
PendingReviewPost
结构体,有一个存储String
类型的content
字段。通过impl
在它上面写了一个approve
方法用于通过审批
这里的Post
就指正式发布之后的Post
,DraftPost
就代表还处于草稿状态的文章,PendingReviewPost
表示正在审批的文章。审批成功就会把content
的值返回到Post
的content
字段里以供使用。
这样写不会出现意外的情况,因为只有通过审批正式发布的状态Post
才有content
方法来获取文章。
此时的main.rs
写法也需要小改:
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());
}
17.3.4. 总结
Rust不仅能够实现面向对象的设计模式,还可以支持更多的模式。例如将状态和行为编码为类型。
面对对象的经典模式并不总是Rust编程实践中的最佳选择,因为Rust具有其他面向对象语言所没有的所有权特性。
相关文章:

【Rust自学】17.3. 实现面向对象的设计模式
喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 17.3.1. 状态模式 状态模式(state pattern) 是一种面向对象设计模式,指的是一个值拥有的内部状态由数个状态对象(…...

51c视觉~CV~合集10
我自己的原文哦~ https://blog.51cto.com/whaosoft/13241694 一、CV创建自定义图像滤镜 热图滤镜 这组滤镜提供了各种不同的艺术和风格化光学图像捕捉方法。例如,热滤镜会将图像转换为“热图”,而卡通滤镜则提供生动的图像,这些图像看起来…...

如何安全地管理Spring Boot项目中的敏感配置信息
在开发Spring Boot应用时,我们经常需要处理一些敏感的配置信息,比如数据库密码、API密钥等。以下是一个最佳实践方案: 1. 创建配置文件 application.yml(版本控制) spring:datasource:url: ${MYSQL_URL:jdbc:mysql…...
Docker小游戏 | 使用Docker部署2048网页小游戏
Docker小游戏 | 使用Docker部署2048网页小游戏 前言项目介绍项目简介项目预览二、系统要求环境要求环境检查Docker版本检查检查操作系统版本三、部署2048网页小游戏下载镜像创建容器检查容器状态检查服务端口安全设置四、访问2048网页小游戏五、总结前言 在当今快速发展的技术世…...
RabbitMQ深度探索:消息幂等性问题
RabbitMQ 消息自动重试机制: 让我们消费者处理我们业务代码的时候,如果抛出异常的情况下,在这时候 MQ 会自动触发重试机制,默认的情况下 RabbitMQ 时无限次数的重试需要认为指定重试次数限制问题 在什么情况下消费者实现重试策略…...

Linux网络 | 进入数据链路层,学习相关协议与概念
前言:本节内容进入博主讲解的网络层级中的最后一层:数据链路层。 首先博主还是会线代友友们认识一下数据链路层的报文。 然后会带大家重新理解一些概念,比如局域网交换机等等。然后就是ARP协议。 讲完这些, 本节任务就算结束。 那…...
芝法酱学习笔记(2.6)——flink-cdc监听mysql binlog并同步数据至elastic-search和更新redis缓存
一、需求背景 在有的项目中,尤其是进销存类的saas软件,一开始为了快速把产品做出来,并没有考虑缓存问题。而这类软件,有着复杂的业务逻辑。如果想在原先的代码中,添加redis缓存,改动面将非常大,…...
JavaScript系列(58)--性能监控系统详解
JavaScript性能监控系统详解 📊 今天,让我们深入探讨JavaScript的性能监控系统。性能监控对于保证应用的稳定性和用户体验至关重要。 性能监控基础概念 🌟 💡 小知识:JavaScript性能监控是指通过收集和分析各种性能指…...

GESP2023年12月认证C++六级( 第三部分编程题(1)闯关游戏)
参考程序代码: #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <string> #include <map> #include <iostream> #include <cmath> using namespace std;const int N 10…...
git 新项目
新项目git 新建的项目如何进行git 配置git git config --global user.name "cc" git config --global user.email ccexample.com配置远程仓库路径 // 添加 git remote add origin http://gogs/cc/mc.git //如果配错了,删除 git remote remove origin初…...
系统URL整合系列视频一(需求方案)
视频 系统URL整合系列视频一(需求方案) 视频介绍 (全国)某大型分布式系统Web资源URL整合需求实现方案讲解。当今社会各行各业对软件系统的web资源访问权限控制越来越严格,控制粒度也越来越细。安全级别提高的同时也增…...
Vue.js 使用组件库构建 UI
Vue.js 使用组件库构建 UI 在 Vue.js 项目中,构建漂亮又高效的用户界面(UI)是很重要的一环。组件库就是你开发 UI 的好帮手,它可以大大提高开发效率,减少重复工作,还能让你的项目更具一致性和专业感。今天…...

计算图 Compute Graph 和自动求导 Autograd | PyTorch 深度学习实战
前一篇文章,Tensor 基本操作5 device 管理,使用 GPU 设备 | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started PyTorch 计算图和 Autograd 微积分之于机器学习Computational Graphs 计算图Autograd…...

51单片机入门_05_LED闪烁(常用的延时方法:软件延时、定时器延时;while循环;unsigned char 可以表示的数字是0~255)
本篇介绍编程实现LED灯闪烁,需要学到一些新的C语言知识。由于单片机执行的速度是非常快的,如果不进行延时的话,人眼是无法识别(停留时间要大于20ms)出LED灯是否在闪烁所以需要学习如何实现软件延时。另外IO口与一个字节位的数据对应关系。 文…...

如何获取sql数据中时间的月份、年份(类型为date)
可用自带的函数month来实现 如: 创建表及插入数据: create table test (id int,begindate datetime) insert into test values (1,2015-01-01) insert into test values (2,2015-02-01) 执行sql语句,获取月份: select MONTH(begindate)…...

【单层神经网络】softmax回归的从零开始实现(图像分类)
softmax回归 该回归分析为后续的多层感知机做铺垫 基本概念 softmax回归用于离散模型预测(分类问题,含标签) softmax运算本质上是对网络的多个输出进行了归一化,使结果有一个统一的判断标准,不必纠结为什么要这么算…...

使用开源项目:pdf2docx,让PDF转换为Word
目录 1.安装python 2.安装 pdf2docx 3.使用 pdf2docx 转换 PDF 到 Word pdf2docx:GitCode - 全球开发者的开源社区,开源代码托管平台 环境:windows电脑 1.安装python Download Python | Python.org 最好下载3.8以上的版本 安装时记得选择上&#…...

保姆级教程Docker部署KRaft模式的Kafka官方镜像
目录 一、安装Docker及可视化工具 二、单节点部署 1、创建挂载目录 2、运行Kafka容器 3、Compose运行Kafka容器 4、查看Kafka运行状态 三、集群部署 四、部署可视化工具 1、创建挂载目录 2、运行Kafka-ui容器 3、Compose运行Kafka-ui容器 4、查看Kafka-ui运行状态 …...

ChatGPT提问技巧:行业热门应用提示词案例--咨询法律知识
ChatGPT除了可以协助办公,写作文案和生成短视频脚本外,和还可以做为一个法律工具,当用户面临一些法律知识盲点时,可以向ChatGPT咨询获得解答。赋予ChatGPT专家的身份,用户能够得到较为满意的解答。 1.咨询法律知识 举…...

openRv1126 AI算法部署实战之——Tensorflow模型部署实战
在RV1126开发板上部署Tensorflow算法,实时目标检测RTSP传输。视频演示地址 rv1126 yolov5 实时目标检测 rtsp传输_哔哩哔哩_bilibili 一、准备工作 从官网下载tensorflow模型和数据集 手动在线下载: https://github.com/tensorflow/models/b…...

第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
Caliper 配置文件解析:config.yaml
Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...