IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> The Rust Programming Language - 第17章 Rust的面向对象编程特性 - 17.3 面向对象设计模式的实现 -> 正文阅读

[Java知识库]The Rust Programming Language - 第17章 Rust的面向对象编程特性 - 17.3 面向对象设计模式的实现

17 Rust的面向对象编程特性

面向对象编程(OOP)是一种模式话编程方式

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

状态模式是一个面向对象设计模式。它的关键在于一个值有很多内部状态,它们叫状态对象,同时每个状态对象都拥有自己的行为以及何时转变为另一种状态。值对状态对象的行为以及状态何时转移毫不知情

使用状态模式的好处在于业务需求发生变化时,我们不需要改变值或者操作值的代码。只需要转变状态对象的状态(包括增加状态对象)或变更每个状态对象的行为即可

我们来通过一个博客发布流程来说明上述功能

博客的功能:

1.博文从空白草案开始

2.一旦草案完成,请求审核博文

3.一旦博文过审,发表博文

4.打印发表的博文

我们将在blog库crate 中实现它

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这个值他有几个内部状态,我们结下来会更加深刻的认识到这一点,测试函数仅仅是为了测试代码如期运行

现在让我们从基本的单位开始构建程序

定义Post并新建一个草案状态的实例

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(),
        }
    }
}
trait State {}
struct Draft {}

impl State for Draft {}

很好,我们创建了这个值的类型Post(调用的时候实例化就行),它是一个结构体,包含两个字段一个内容一个状态,状态还是可变的,在这里我们使用了一个Option枚举,里面装了一个trait对象 Box,没错,这里的操作和上一节中学过的内容时一致的

接着我们为Post定义了一个关联函数,函数返回的是一个Post实例,Post实例中content字段我们使用了String::from创建了一个空字符串,但是state字段我们使用了实现了State trait的一个一个结构体,很妙,一个状态对象就实现了

存放博文内容的文本

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

为Post增加一个方法,用来为content中增加内容,这个非常简单直接定义就行了,它是和字段state无关的

确保博文草案的内容是空的

因为我们在现阶段需要保证博文是草案状态,所以即使调用add_text函数向博文中增加了内容之后,我们仍然需要让博文是一个草案,我们来再为Post增加一个字段同名方法content,让它总返回空字符串,这就实现我们的阶段需要

impl Post {
    pub fn content(&self)->&str {
        ""
    }
}

请求审核博文来改变其状态

前面我们已经实现了保持博文为草稿状态,并且能为其中增开内容。现在我们来让编辑完成的博文请求审核

前面博文的状态是由结构体Draft控制的,因此我们再来定义一个新结构体改变其状态

impl Post {
    pub fn request_review(&mut self) {
        if let Some(s) = self.state.take(){
            self.state = Some(s.request_review())
        }
    }
}

trait State {
    fn request_review(self:Box<Self>) -> Box<dyn State>;
}
struct Draft {}

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

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

我们定义了一个结构体PendingReview,并且为它实现了 State trait,这样它就可以替代Draft了(因为它们都实现了 State trait)。那它到底怎么实现的呢?我们先为Post增加了方法request_review,它里面还有一个request_review方法,它会消费当前状态返回一个新状态。这个实现就这么完成了,我们进一步来看看

我们为 trait State增加了request_review方法(这里只定义了签名),这意味着所实现了 State trait的类型都需要实现request_review方法,具体方法体有一点点区别,前者是新建了一个PengingReview,后者是自己(也是PendingReview)

回到Post里的方法,我们使用了if let进行了匹配,并且把取出的老值作为参数给了request_review方法,它返回了一个新值PengingReview{}. Ok,这下真相大白了

增加改变content行为的approve方法

approve方法和request_review方法功能一样,我们为 State trait增加了approve方法,同样的,我们也为实现了State trait的类型统统增加了approve方法,并且它能切换状态

impl Post {
   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> {
        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
    }
}

现在博文已经发布了,我们返回它的content

impl Post {
   pub fn content(&self)->&str {
        self.state.as_ref().unwrap().content(self)
    }
}
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{
        ""
    }
}

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
    }
}

这里调用as_ref方法是因为需要Option中值的引用而不是获取其所有权,State是一个Option<Box>,调用as_ref会返回一个Option<&Box>

注意这个方法需要生命周期注解,这里获取post的引用作为参数,并返回post一部分的引用。所返回的引用的生命周期与post相关

好了,我么的例子讲完了,虽然代码量挺多,但是逻辑还算简单

状态模式的权衡与取舍

想象一下,如果我们不使用状态模式实现上述项目,我们可能会用一些match语句,会比较繁琐

但是使用状态模式,如果我们需要状态相互联系,如果在某些状态间增加状态,那就需要修改代码了。另一缺点是一些逻辑重复,还有一个缺点是调用时的重复如调用同一方法,后面我们常使用宏来修复这个问题

将状态和行为编码为类型

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

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

我们仍然可以新建一个post,但是这将使博文草案完全没有content方法

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);
    }
}

通过类似上面的编译,我们能实现我们想要的功能,同时不想完全封装和状态转移,外部代码毫不知情

上述代码确保了博文从草案开始,并且草案博文没有任何展示内容

实现状态转移为不同类型的转换

我们来定义草案博文在发布之前必须被审核通过

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函数也得修改

request_review和approve返回新实例而不是修改被调用的结构体,所我们要增加更多的let 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());
}

我们的修改使得实现不再完全遵守面向对象的状态模式;状态间的转换不再完全封装在Post实现中。得益于类型系统和编译时类型检查,一些bug将会在部署到生产环境之前被发现

即便Rust能够实现面向对象设计模式,也有其他像将状态编码进类型这样的模式存在,这些模式有着不同的权衡取舍。所以要综合使用,某一种方法也许不一定是最好的方法

总结:Trait对象是一个Rust中获取部分面向对象功能的方法,动态分发可以通过牺牲少量运行时增加代码灵活性,这有助于实现代码可维护性的面向对象模式。Rust也有所有权这样不同于面向对象语言的功能

模式让Rust的功能变得多样和灵活,下一章我们将会一起学习

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-12-01 17:34:35  更:2021-12-01 17:36:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:32:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码