rust学习-面向对象

这篇具有很好参考价值的文章主要介绍了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 实现中
得益于类型系统和编译时类型检查,得到无效状态是不可能的
上述的取舍真的牛逼!!!文章来源地址https://www.toymoban.com/news/detail-602972.html

到了这里,关于rust学习-面向对象的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 设计模式学习笔记 - 面向对象 - 3.面向对象比面向过程有哪些优势?面向过程真的过时了吗?

    在过往的工作中,我发现很多人搞不清面向对象和面向过程的区别,总认为使用面向对象编程语言来开发,就是在面向面向对象编程了。而实际上,他们只是在用面向对象编程语言,编写面向过程风格的代码而已,并没有发挥面向对象编程的优势。这就相当于手握一把屠龙刀

    2024年02月22日
    浏览(36)
  • 学习笔记整理-面向对象-01-认识对象

    1. 对象 对象(object)是 键值对 的集合,表示属性和值的 映射关系 。 对象的语法 k和v之间用冒号分割,每组 k:v 之间用逗号分割,最后一个 k:v 对后可以不书写逗号。 属性是否加引号 如果对象的属性键名不符合命名规范,则这个键名必须用引号包裹。 属性的访问 可以用 点语

    2024年02月13日
    浏览(32)
  • 设计模式学习笔记 - 面向对象 - 1.面向对象到底讨论的是什么

    面向对象编程( OOP )的全称是 Object Oriented Programming 。 面向对象编程语言( OOPL )的全称是 Object Oriented Programming Language 。 面向对象编程中有两个非常重要的概念,就是类( Class )和对象( Object )。面向对象编程这个概念第一次使用是在 SmallTalk 这种编程语言中,它也被认

    2024年02月22日
    浏览(34)
  • C#核心学习(面向对象)

    基本概念: 在实例化对象时,会调用的用于初始化的函数,如果不写,则默认存在一个无参构造函数。 写法: 1、没有返回值 2、函数名和类名必须相同 3、没有特殊需求时,一般是 public 4、构造函数可以被重载 5、this代表当前调用该函数的对象自己 特殊写法 可以通过this 重

    2024年02月03日
    浏览(27)
  • 【Python学习】—面向对象(九)

    类中不仅可以定义属性来记录数据,也可以定义函数,用来记录行为,类中定义的属性(变量)我们称之成员变量,类中定义的行为(函数),我们称之为成员方法。 表示对象本身的意思 只有通过self,成员方法才能访问类的成员变量 self出现在形参列表中,但是不占用参数

    2024年02月08日
    浏览(36)
  • 面向对象 学习黑马视频(03)

    构造函数 析构函数(1) 构造函数 析构函数(2) 包含:构造函数顺序和析构函数顺序 成员变量 成员函数 C++对象模型 this指针 空指针访问成员函数 const 修饰成员函数 ;常对象 友元,让其他类可以访问本类中的私有变量 (1)全局函数做友元 (2)友元类 (3)成员函数 友元

    2024年02月10日
    浏览(27)
  • Java面向对象学习笔记-4

    当编写Java程序时,了解如何处理异常是至关重要的。异常处理可以帮助我们在程序执行过程中处理各种错误情况,以确保程序能够正常运行或者 graceful 地退出。本文将介绍一些关于Java异常处理的基本概念和最佳实践,包括自定义异常类、用户输入的处理、异常捕获与处理、

    2024年02月09日
    浏览(32)
  • Python学习之路-初识面向对象

    面向对象编程(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的编程典范,同时也是一种程序开发的抽象方针。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。

    2024年01月21日
    浏览(43)
  • Java学习笔记(三):面向对象

    类(class)和对象(object, 也被称为实例 instance)是 java面向对象的核心,可以把类理解成某种概念,对象理解为一个具体存在的实体。也就是说,日常说的人,其实都是人的实例,而不是人类。 定义类的简单语法: 修饰符可以是 public、final、abstract,或者完全省略。 对一个类而

    2024年02月11日
    浏览(39)
  • Python学习笔记(二十)————面向对象

    (1)面向对象的好处 在日常中,记录数据时往往使用统一的表格,这样就不会使得数据信息格式混乱,同样在程序中的数据组织中,仅仅通过变量来记录会显得混乱不统一。 在程序中是可以做到和生活中那样,设计表格、生产表格、填写表格的组织形式的。 1. 在程序中 设

    2024年02月13日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包