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
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550646.html
標籤:其他
下一篇:返回列表
