写点什么

5 分钟速读之 Rust 权威指南(三十四)面向对象

用户头像
码生笔谈
关注
发布于: 3 小时前
5分钟速读之Rust权威指南(三十四)面向对象

特点介绍

前面的章节读完后,就已经掌握了 rust 的基本使用,但是还没有讲过 rust 的编程范式,这一节我们来了解 rust 的面向对象特性,我们都知道面向对象包含三个特性:封装、继承、多态,下面就从这三个点来看 rust 是如何设计面向对象编程的

封装

下面我们使用封装特性,实现一个对外暴露几个简单方法的结构体,用于自动计算平均数:


// src/average.rs// 定义一个结构体pub struct AveragedCollection {  // 用于求平均值的数组  list: Vec<i32>,  // 当前list数组的平均值  average: f64,}
impl AveragedCollection { // 为使用方提供新建一个集合的方法 pub fn new() -> Self { Self { list: vec![], average: 0 as f64, } } // 为使用方提供添加一个数字的方法 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; }}
复制代码


简单使用 AveragedCollection 结构体:


// src/main.rsmod average;use average::AveragedCollection;
let mut ac = AveragedCollection::new();
// 添加一些数值ac.add(1);ac.add(2);ac.add(3);ac.add(4);println!("average(): {}", ac.average());// 2.5
// 删除一个数值ac.remove();println!("average(): {}", ac.average());// 2
println!("field average: {}", ac.average); // 报错,不能访问私有字段println!("field list: {:?}", ac.list); // 报错,不能访问私有字段
复制代码


上面代码中使用 pub 关键字来实现对细节的封装,只对外暴露 new、add、remove、average 方法。由于 list 字段不是对外公开的,所以未来我们改变集合的实现时,使用方也无需改动代码,例如我们可以在 list 字段上使用 HashSet<i32>代替 Vec<i32>。

继承

rust 并不提供传统意义上的继承,作为替代解决方案,我们可以使用 Rust 中的默认 trait 方法来进行代码共享:


trait Dog {  // 方法的默认实现  fn say(&self) {    println!("汪汪~")  }}
struct Husky {}impl Dog for Husky {}
let husky = Husky {};// 使用方法的默认实现husky.say() // 汪汪~
复制代码

多态

我们可以在 Rust 中使用泛型来构建不同类型的抽象,并使用 trait 约束来决定类型必须提供的具体特性。这也被称作限定参数化多态(bounded parametric polymorphism)。




下面示例创建了一个图形用户界面(Graphical User Interface,GUI)包。这个工具会遍历某个元素列表,并依次调用元素的 draw 方法来将其绘制到屏幕中,包里对外暴露了许多内置组件,比如 Button、Text。另外,包用户也可以创建自定义类型,例如,某些开发者可能会添加 Image,而另外某些开发者则可能会添加 Select。

定义 Draw trait

每一个图形都必须实现 Draw trait:


pub trait Draw {  fn draw(&self); // 将会在Screen的run方法中被调用}
复制代码

定义 Screen

下面定义 Screen 结构体,包含的 components 属性用于存储要绘制的图形:


pub struct Screen<T: Draw> {  // 包含要绘制的图形,要求实现Draw trait  pub components: Vec<T>}
impl<T: Draw> Screen<T> { pub fn run(&self) { // 依次调用每个组件的draw方法 for item in self.components.iter() { item.draw() } }}
复制代码

定义组件

实现了 Screen,下面来实现两个图形组件,Button 和 Text,并分别为他们实现 Draw trait:


// 按钮组件pub struct Button {  pub width: u32,  pub height: u32,  pub label: String}// 实现Draw traitimpl Draw for Button {  fn draw(&self) {    println!("绘制一个Button")  }}
// 文本组件pub struct Text { pub width: u32, pub height: u32, pub placeholder: String}// 实现Draw traitimpl Draw for Text { fn draw(&self) { println!("绘制一个文本"); }}
复制代码

实现绘制逻辑

接下来初始化画布并定义图形组件:


let screen = Screen {  components: vec![    Text {      width: 100,      height: 100,      placeholder: String::from("请输入文本"),    },    Button { // 报错,预期一个Text结构体,却发现了一个Button      width: 100,      height: 100,      label: String::from("确认"),    },  ]};
screen.run()
复制代码


上面在 components 的成员中,第二个成员 Button 出现了编译错误,原因在于泛型参数一次只能被替代为一个具体的类型,上面代码中由于第一个类型是 Text 类型,所以当传入结构体 Button 时,产生了编译错误。

使用 trait 对象

对于上面问题,我们可以利用 trait 对象来解决,trait 对象允许在运行时填入多种不同的具体类型:


pub struct Screen {  // 这个动态数组的元素类型使用了新语法Box<dyn Draw>来定义trait对象  // 它被用来代表所有被放置在Box中且实现了Draw trait  pub components: Vec<Box<dyn Draw>>}
复制代码


重新实现绘制逻辑:


let screen = Screen {  components: vec![    // 使用Box来符合Draw trait    Box::new(Text {      width: 100,      height: 100,      placeholder: String::from("请输入文本"),    }),    Box::new(Button {      width: 100,      height: 100,      label: String::from("确认"),    }),  ]};screen.run()// 绘制一个文本// 绘制一个Button
复制代码


rust 的多态性体现在实现 run 方法的过程中并不需要知晓每个组件的具体类型,它仅仅调用了组件的 draw 方法,而不会去检查某个组件究竟是 Button 实例还是 Text 实例。通过在定义动态数组 components 时指定 Box<dyn Draw>元素类型,Screen 实例只会接收那些能够调用 draw 方法的值。


封面图:跟着Tina画美国

发布于: 3 小时前阅读数: 2
用户头像

码生笔谈

关注

欢迎关注「码生笔谈」公众号 2018.09.09 加入

前端、Rust干货分享,一起成为更好的Javascripter & Rustacean

评论

发布
暂无评论
5分钟速读之Rust权威指南(三十四)面向对象