Rust編程語言入門之Rust的面向對象編程特性

来源:https://www.cnblogs.com/QiaoPengjun/archive/2023/04/20/17338539.html
-Advertisement-
Play Games

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在JavaScript中, for...in 和 for...of 都是用於迭代迴圈的結構: 1. for...in 迴圈: for...in 迴圈主要用於遍歷對象的可枚舉屬性。這種迴圈不僅遍歷對象自身的屬性,還會遍歷原型鏈上的可枚舉屬性。它的語法如下: for (variable in objec ...
  • 簡介 備忘錄模式(Memento Pattern)是一種結構型設計模式。這種模式就是在不破壞封裝的條件下,將一個對象的狀態捕捉(Capture)住,並放在外部存儲起來,從而可以在將來合適的時候把這個對象還原到存儲起來的狀態。備忘錄模式常常與命令模式和迭代子模式一同使用。 備忘錄模式的角色有三個:備忘 ...
  • 結合京東業務研發實際情況,針對後端研發人員,設計一個微服務低代碼平臺,助力更高效低交付業務需求。現已結業,將我在本次項目中沉澱設計出的設計文檔整理成文,期待與大家有進一步的碰撞溝通 ...
  • 下載Opencv 先去官網https://opencv.org/opencv-4-7-0/下載, 找到適合你設備的版本下載Windows就是Win pack,完成後進行安裝即可,一路同意預設就行,可以更改安裝位置,但路徑上儘可能以英文,以防止後面不必要的問題。 2.下載Python 首先是版本 發文 ...
  • 說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。本篇介紹 VLD 源碼的下載。同系列文章目錄可見 《記憶體泄漏檢測工具》目錄 1. 下載途徑 以 v2.5.1 版本為例,可以到 Github-KindDragon-vld 頁面下載 master 的 zip 源碼包,如下所示: 也可以到 ...
  • 相信大家在學習 Java 的過程中,或多或少都見過 JSR 這個詞。本篇文章就科普下什麼是 JSR。 什麼是 JSR ? JSR(Java Specification Requests),是指 Java 規範請求(或者活規範提案)。這個請求(提案)是提給 JCP 的(Java Community P ...
  • 上周末,去參加了北京思否 hackathon,兩天時間內從腦暴 & 挖掘軟體 IDEA -> Demo 研發路演,這次經歷讓我難忘。這裡我的看法是每個開發者圈友,都應該去參加一次 hackathon ~ 做 niche 軟體正確的方法 這邊先說結論,如圖。我認為 做 niche 軟體正確的方法 或 ...
  • JVM中集成了兩種編譯器,Client Compiler和Server Compiler,它們的作用也不同。Client Compiler註重啟動速度和局部的優化,Server Compiler則更加關註全局的優化,性能會更好,但由於會進行更多的全局分析,所以啟動速度會變慢。兩種編譯器有著不同的應用 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...