Rust 的面向對象編程特性 一、面向對象語言的特性 Rust是面向對象編程語言嗎? Rust 受到多種編程範式的影響,包括面向對象 面向對象通常包含以下特性:命名對象、封裝、繼承 對象包含數據和行為 “設計模式四人幫”在《設計模型》中給面向對象的定義: 面向對象的程式由對象組成 對象包裝了數據和操 ...
Rust 的面向對象編程特性
一、面向對象語言的特性
Rust是面向對象編程語言嗎?
- Rust 受到多種編程範式的影響,包括面向對象
- 面向對象通常包含以下特性:命名對象、封裝、繼承
對象包含數據和行為
- “設計模式四人幫”在《設計模型》中給面向對象的定義:
- 面向對象的程式由對象組成
- 對象包裝了數據和操作這些數據的過程,這些過程通常被稱作方法或操作
- 基於此定義:Rust是面向對象的
- struct、enum 包含數據
- impl 塊為之提供了方法
- 但帶有方法的 struct、enum 並沒有被稱為對象
封裝
- 封裝:調用對象外部的代碼無法直接訪問對象內部的實現細節,唯一可以與對象進行交互的方法就是通過它公開的 API
- Rust:pub 關鍵字
pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
impl AveragedCollection {
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:預設 trait 方法來進行代碼共用
- 多態
- Rust:泛型和 trait 約束(限定參數化多態 bounded parametric)
- 代碼復用
- 很多新語言都不使用繼承作為內置的程式設計方案了。
二、使用 trait 對象來存儲不同類型的值
有這樣一個需求
- 創建一個 GUI 工具:
- 它會遍歷某個元素的列表,依次調用元素的 draw 方法進行繪製
- 例如:Button、TextField 等元素
- 在面向對象語言里:
- 定義一個 Component 父類,裡面定義了 draw 方法
- 定義 Button、TextField 等類,繼承與 Component 類
為共有行為定義一個 trait
- Rust 避免將 struct 或 enum 稱為對象,因為他們與 impl 塊是分開的
- trait 對象有些類似於其它語言中的對象:
- 它們某種程度上組合了數據與行為
- trait 對象與傳統對象不同的地方:
- 無法為 trait 對象添加數據
- trait 對象被專門用於抽象某些共有行為,它沒其它語言中的對象那麼通用
Trait 動態 lib.rs 文件
pub trait Draw {
fn draw(&self);
}
pub struct Screen {
pub components: Vec<Boc<dyn Draw>>,
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
pub struct Button {
pub width: u32,
pub height: u32,
pub label: String,
}
impl Draw for Button {
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()
}
}
}
main.rs 文件
use oo::Draw;
use oo::{Button, Screen};
struct SelectBox {
width: u32,
height: u32,
options: Vec<String>,
}
impl Draw for SelectBox {
fn draw(&self) {
// 繪製一個選擇框
}
}
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();
}
Trait 對象執行的是動態派發
- 將 trait 約束作用於泛型時,Rust編譯器會執行單態化:
- 編譯器會為我們用來替換泛型參數的每一個具體類型生成對應函數和方法的非泛型實現。
- 通過單態化生成的代碼會執行靜態派發(static dispatch),在編譯過程中確定調用的具體方法
- 動態派發(dynamic dispatch):
- 無法在編譯過程中確定你調用的究竟是哪一種方法
- 編譯器會產生額外的代碼以便在運行時找出希望調用的方法
- 使用 trait 對象,會執行動態派發:
- 產生運行時開銷
- 阻止編譯器內聯方法代碼,使得部分優化操作無法進行
Trait 對象必須保證對象安全
- 只能把滿足對象安全(object-safe)的 trait 轉化為 trait 對象
- Rust採用一系列規則來判定某個對象是否安全,只需記住兩條:
- 方法的返回類型不是 Self
- 方法中不包含任何泛型類型參數
lib.rs 文件
pub trait Draw {
fn draw(&self);
}
pub trait Clone {
fn clone(&self) -> Self;
}
pub struct Screen {
pub components: Vec<Box<dyn Clone>>, // 報錯
}
三、實現面向對象的設計模式
狀態模式
- 狀態模式(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());
}
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> {
self
}
}
struct PendingReview {}
impl State for PendingRevew {
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
}
}
修改之後:
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 {
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())
}
}
}
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 {
""
}
}
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 PendingRevew {
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
}
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}
狀態模式的取捨權衡
- 缺點:
- 某些狀態之間是相互耦合的
- 需要重覆實現一些邏輯代碼
將狀態和行為編碼為類型
- 將狀態編碼為不同的類型:
- 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,
}
}
}
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());
}
總結
- Rust 不僅能夠實現面向對象的設計模式,還可以支持更多的模式
- 例如:將狀態和行為編碼為類型
- 面向對象的經典模式並不總是 Rust 編程實踐中的最佳選擇,因為 Rust具有所有權等其它面向對象語言沒有的特性!
本文來自博客園,作者:QIAOPENGJUN,轉載請註明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17338539.html