Rust 語言設計選擇不支援可選引數和函式過載,以確保與 C 語言相容,並避免潛在問題。然而,開發者可以運用特徵的彈性,結合泛型和特徵約束,巧妙地模擬可選引數的功能。透過定義不同的特徵和對應方法,搭配泛型函式,就能根據不同的特徵約束,呼叫對應的方法,達到類別似可選引數的效果。這種方式雖然不像 C++ 直接,但更具彈性,也更符合 Rust 的設計哲學。此外,建造者模式提供另一種解決方案,它允許開發者逐步構建複雜物件,避免過多的建構子引數,並提升程式碼可讀性。透過定義 Builder 結構體和一系列 setter 方法,開發者可以按需設定物件屬性,最後再呼叫 build 方法,取得完整的物件例項。
5.2.2 檢視C++中的可選引數
C++允許透過函式過載使用可選的函式引數。也就是說,在C++中可以有多個具有不同引數的函式定義,並且這些函式可以為任何缺失的引數提供預設值。這種模式在C++中可能看起來像這樣,有三個過載的函式:
void func() {
func(true, 11);
}
void func(optional_bool: bool) {
func(optional_bool, 11);
}
void func(optional_bool: bool, optional_int: int) {
// ... 函式主體在此 ...
}
內容解密:
此程式碼展示了C++中如何透過函式過載實作可選引數。第一個func()
呼叫第二個func(bool)
,並傳入預設的布林值true
和整數11
。第二個func(bool)
再呼叫第三個func(bool, int)
,傳入布林值和整數11
。這種方式讓呼叫者可以省略某些引數,而由預設值補充。
C++透過對函式名稱進行混淆(mangling)來實作這一點,這使得C++函式與根據C的函式庫不相容。從C++呼叫C程式碼很容易,但反之則最好避免。
5.2.3 Rust中的可選引數或缺乏可選引數
Rust明確地缺乏可選引數或函式過載是一種設計選擇,部分是為了與C相容,部分是為了避免前述章節中提到的批評。然而,我們可以在一定程度上模擬這些功能。我們有三種選擇:
- 使用特徵(traits)進行擴充套件
- 使用巨集(macros)在編譯時匹配引數
- 使用
Option
包裝引數
我們將重點關注第一種模式:使用特徵進行擴充套件。
5.2.4 使用特徵模擬可選引數
首先,我們將展示可以為一個結構體實作具有衝突方法名稱的兩個特徵:
struct Container {
name: String,
}
trait First {
fn name(&self) {}
}
trait Second {
fn name(&self) {}
}
impl First for Container {
fn name(&self) {}
}
impl Second for Container {
fn name(&self) {}
}
內容解密:
此段程式碼定義了一個名為Container
的結構體和兩個特徵First
與Second
,這兩個特徵都有一個名為name
的方法。然後為Container
實作了這兩個特徵。這樣做的結果是,當我們嘗試呼叫container.name()
時,編譯器會報錯,因為它無法確定應該呼叫哪個name
方法。
接下來,如果特徵方法具有不同的簽名會怎麼樣?讓我們為Second
特徵的方法新增一個布林引數:
trait First {
fn name(&self) {}
}
trait Second {
fn name(&self, _: bool) {}
}
impl First for Container {
fn name(&self) {}
}
impl Second for Container {
fn name(&self, _: bool) {}
}
內容解密:
即使方法簽名不同,編譯器仍然會報錯,因為直接呼叫container.name()
仍然存在歧義。
然後,我們可以使用特徵約束來定義兩個函式:
fn get_name_from_first<T: First>(t: &T) {
t.name()
}
fn get_name_from_second<T: Second>(t: &T) {
t.name(true)
}
內容解密:
這兩個函式分別呼叫了來自First
和Second
特徵的name
方法。透過使用泛型和特徵約束,我們可以告訴編譯器我們想要使用哪個方法。這樣,當我們呼叫get_name_from_first(&container)
或get_name_from_second(&container)
時,編譯器就知道該使用哪個方法了。
5.3 Builder模式
Builder模式是《設計模式》中描述的原始模式之一。這個模式在軟體設計中變得非常流行,並且(除了迭代器之外)可以說是該書中最持久的模式之一。
Builder模式也可以被視為一種柯里化(currying)的方式,即將一個接受多個引數的函式轉換為一系列接受一個引數的函式。
為什麼使用Builder模式?
在Rust中,我們通常不希望直接暴露結構體,而且如前所述,Rust不支援可選引數。因此,與其依賴具有大量引數的建構子,我們可以使用Builder來處理更複雜的情況。
5.3.1 實作Builder模式
讓我們為我們想要建模的腳踏車寫一個基本的Builder。我們將建模圖5.1所示的關係。
// 定義Bicycle結構體和它的Builder
struct Bicycle {
brand: String,
size: u32,
gear: u8,
}
struct BicycleBuilder {
brand: Option<String>,
size: Option<u32>,
gear: Option<u8>,
}
impl BicycleBuilder {
fn new() -> Self {
BicycleBuilder {
brand: None,
size: None,
gear: None,
}
}
fn brand(mut self, brand: String) -> Self {
self.brand = Some(brand);
self
}
fn size(mut self, size: u32) -> Self {
self.size = Some(size);
self
}
fn gear(mut self, gear: u8) -> Self {
self.gear = Some(gear);
self
}
fn build(self) -> Result<Bicycle, &'static str> {
match (self.brand, self.size, self.gear) {
(Some(brand), Some(size), Some(gear)) => Ok(Bicycle { brand, size, gear }),
_ => Err("Missing required fields"),
}
}
}
內容解密:
這段程式碼定義了一個Bicycle
結構體和一個BicycleBuilder
結構體。BicycleBuilder
用於逐步構建Bicycle
例項。它的方法(如brand
、size
和gear
)傳回Builder本身,使得可以鏈式呼叫。最後,build
方法檢查所有必要欄位是否已設定,如果是,則傳回一個Bicycle
例項;否則,傳回一個錯誤訊息。
fn main() {
let bicycle = BicycleBuilder::new()
.brand("Trek".to_string())
.size(52)
.gear(21)
.build();
match bicycle {
Ok(b) => println!("Built bicycle: {:?}, size: {:?}, gear: {:?}", b.brand, b.size, b.gear),
Err(e) => println!("Error building bicycle: {}", e),
}
}
內容解密:
在 main
函式中,我們使用 BicycleBuilder
建立了一個 Bicycle
例項,並列印出結果。如果所有必要欄位都已正確設定,則會成功建立並列印腳踏車的詳細資訊;否則,會列印錯誤訊息。
建造者模式:超越基礎
建造者模式簡介
建造者模式是一種建立型設計模式,旨在將複雜物件的建構過程與其表示分離,從而使得相同的建構過程可以建立不同的表示。在本章中,我們將探討建造者模式在 Rust 程式語言中的實作。
基本實作
首先,我們定義一個 Bicycle
結構體來表示一輛腳踏車:
#[derive(Debug)]
pub struct Bicycle {
make: String,
model: String,
size: i32,
color: String,
}
impl Bicycle {
accessor!(make, &String);
accessor!(model, &String);
accessor!(size, i32);
accessor!(color, &String);
}
內容解密:
Bicycle
結構體包含四個屬性:make
、model
、size
和color
,分別代表腳踏車的品牌、型號、尺寸和顏色。accessor!
巨集用於為Bicycle
的屬性生成 getter 方法。根據屬性的型別不同,accessor!
巨集會生成傳回參照或複製的方法。
接下來,我們實作 BicycleBuilder
結構體及其方法:
pub struct BicycleBuilder {
bicycle: Bicycle,
}
impl BicycleBuilder {
pub fn new() -> Self {
Self {
bicycle: Bicycle {
make: String::new(),
model: String::new(),
size: 0,
color: String::new(),
},
}
}
with_str!(make, with_make);
with_str!(model, with_model);
with!(size, with_size, i32);
with_str!(color, with_color);
pub fn build(self) -> Bicycle {
self.bicycle
}
}
內容解密:
BicycleBuilder
結構體包含一個Bicycle
例項,用於在建構過程中暫存腳踏車的屬性。new
方法初始化一個新的BicycleBuilder
例項,並將Bicycle
的屬性設為預設值。with_str!
和with!
巨集用於為BicycleBuilder
生成設定屬性的方法。這些方法允許我們以鏈式呼叫的方式設定腳踏車的屬性。build
方法傳回建構好的Bicycle
例項。
使用建造者模式
現在,我們可以使用 BicycleBuilder
來建立 Bicycle
例項:
fn main() {
let mut bicycle_builder = BicycleBuilder::new();
bicycle_builder.with_make("Huffy");
bicycle_builder.with_model("Radio");
bicycle_builder.with_size(46);
bicycle_builder.with_color("red");
let bicycle = bicycle_builder.build();
println!("My new bike: {:#?}", bicycle);
}
內容解密:
- 我們首先建立一個新的
BicycleBuilder
例項。 - 然後,我們使用
with_make
、with_model
、with_size
和with_color
方法設定腳踏車的屬性。 - 最後,我們呼叫
build
方法來取得建構好的Bicycle
例項,並列印出來。
擴充套件建造者模式
為了使建造者模式更具通用性,我們可以定義一個 Builder
特徵:
pub trait Builder<T> {
fn new() -> Self;
fn build(self) -> T;
}
並為 BicycleBuilder
實作這個特徵:
impl Builder<Bicycle> for BicycleBuilder {
fn new() -> Self {
// ...
}
fn build(self) -> Bicycle {
// ...
}
}
同時,我們也可以定義一個 Buildable
特徵,用於提供取得建造者例項的方法:
pub trait Buildable<Target, B: Builder<Target>> {
fn builder() -> B;
}
impl Buildable<Bicycle, BicycleBuilder> for Bicycle {
fn builder() -> BicycleBuilder {
BicycleBuilder::new()
}
}
這樣,我們就可以透過 Bicycle::builder()
方法直接取得一個新的 BicycleBuilder
例項。
在未來,我們可以進一步探索建造者模式在其他領域的應用,例如在圖形介面程式設計或遊戲開發中。同時,我們也可以研究如何將建造者模式與其他設計模式結合使用,以解決更複雜的問題。
Mermaid 圖表說明
classDiagram class Bicycle { -String make -String model -Integer size -String color +make() : String +model() : String +size() : Integer +color() : String } class BicycleBuilder { -Bicycle bicycle +with_make(String) +with_model(String) +with_size(Integer) +with_color(String) +build() : Bicycle } BicycleBuilder --> Bicycle : builds
圖表翻譯:
此圖表展示了 Bicycle
和 BicycleBuilder
之間的關係。BicycleBuilder
負責建立 Bicycle
例項,並提供了多個方法來設定腳踏車的屬性。最終,build
方法傳回建構好的 Bicycle
例項。