Rust - 訪問者模式 vs 枚舉

訪問者模式是爲一組類添加一個操作,而不修改每個類本身。

Rust 中實現訪問者模式,假設我們有兩種具體的數據類型:X 和 Y,爲簡單起見,我們假設這些類只是沒有方法的純結構體,因此稱之爲 Data。同時我們定義 Action Trait 表示我們想要應用於 Data 上的操作。

下面的代碼是實現訪問者模式的最小樣板代碼:

trait Data {
    fn call<A: Action>(&self, action: &A) -> A::Result;
}

trait Action {
    type Result;
    fn act_x(&self, x: &X) -> Self::Result;
    fn act_y(&self, y: &Y) -> Self::Result;
}

struct X {
    // ...
}

impl Data for X {
    fn call<A: Action>(&self, action: &A) -> A::Result {
        action.act_x(self)
    }
}

struct Y {
    // ...
}

impl Data for Y {
    fn call<A: Action>(&self, action: &A) -> A::Result {
        action.act_y(self)
    }
}

爲了添加一個動作,我們創建了實現 action 的結構體:

struct Action1;
impl Action for Action1 {
    type Result = i32;
    fn act_x(&self, x: &X) -> Self::Result { 1 }
    fn act_y(&self, x: &Y) -> Self::Result { 2 }
}


struct Action2;
impl Action for Action2 {
    type Result = &'static str;

    fn act_x(&self, x: &X) -> Self::Result { "x" }
    fn act_y(&self, x: &Y) -> Self::Result { "y" }
}

現在我們有了 Data = {X, Y} 和 Action = {Action1, Action2},我們就可以得到所有可能的組合。

fn main() {
    let x = X{ /* ... */ };
    let y = Y{ /* ... */ };
    let a1 = Action1;
    let a2 = Action2;

    // {X, Y} x {Action1, Action2}
    let result_x1 = x.call(&a1);
    let result_x2 = x.call(&a2);
    let result_y1 = y.call(&a1);
    let result_y2 = y.call(&a2);
}

通過這種方式,我們可以在不修改類本身的情況下向現有的類添加任意多個操作。缺點是它使代碼冗長。訪問者模式的替代方案是什麼?

對於 Rust,我們可以使用 enum 模式匹配:

enum Data {
    X(X),
    Y(Y),
}

struct X {
    // fill in
}

struct Y {
    // fill in
}

fn action1(data: &Data) -> i32 {
    match data {
        Data::X(x) => 1,
        Data::Y(y) => 2,
    }
}

fn action2(data: &Data) -> &'static str {
    match data {
        Data::X(x) ="x",
        Data::Y(y) ="y",
    }
}
fn perform_all_combinations() {
    let x = Data::X(X{ /* ... */ });
    let y = Data::Y(Y{ /* ... */ });

    let x1 = action1(&x);
    let x2 = action2(&x);
    let y1 = action1(&y);
    let y2 = action2(&y);
}

這極大地簡化了代碼,這是否意味着我們應該總是使用枚舉模式匹配而不是訪問者模式呢?

通常情況下,天下沒有免費的午餐。枚舉模式匹配的一個缺點是,通過創建枚舉,Data 的對象大小將被設置爲其變體中最大的,因此可能會浪費內存空間。可以通過將 X 和 Y 包裝到 Box 中來緩解這個問題,但這本身又引入了另一種間接方式,爲動態內存分配帶來了額外的開銷。

然而,訪問者模式的典型用例可能需要在運行時動態確定數據類型,因此訪問者模式也可能需要 Box 開銷。

總之,這兩種模式讓我們實現了同樣的事情,我認爲沒有哪一種模式在所有方面都比另一種更好。更確切地說,這可能取決於個人對代碼組織方式的偏好。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/75qrem5U7PVsBzdmsTDjfQ