Rust語言之GoF設計模式:工廠模式

banq 發表於 2022-09-25
設計模式 Go

工廠模式的是將建立邏輯封裝在一個方法中,在 "外部"實現對其使用。
(banq::老子道德經中“無以為用”,“無”的意思就是跳出事物內部細節,從事物外部才能使用它。Rust的事物內部和外部邊界很嚴格,所有權概念使然。)

簡單工廠
簡單工廠模式只是一個帶有大條件的函式,根據引數選擇要例項化的產品,然後返回,因為沒有繼承和複雜的建立特徵,因此它是簡單工廠。

create_button是一個功能(一個事物):建立隨機按鈕:

fn create_button(random_number: f64) -> Box<dyn Button> {
    if random_number < 0.5 {
        Box::new(TitleButton::new("Button".to_string()))
    } else {
        Box::new(IdButton::new(123))
    }
}


而render_dialog則使用從create_button得到的任何結果,再對其進行操作:

fn render_dialog(random_number: f64) {
    // ...
    let button = create_button(random_number);
    button.render();
    // ...
}


工廠方法
工廠方法是一種建立設計模式,它提供了在超特徵中建立物件的介面,但允許子特徵改變將要建立的物件的型別。

案例一:以建立對話方塊按鈕為例:

主介面Dialog:gui.rs

pub trait Button {
    fn render(&self);
    fn on_click(&self);
}

/// Dialog有一個工廠方法`create_button`。
///
/// 它根據工廠的實現來建立不同的按鈕。
pub trait Dialog {
    /// 這裡是工廠方法,必須有一個具體的實現來覆蓋,具體實現有兩個。
    fn create_button(&self) -> Box<dyn Button>;

    fn render(&self) {
        let button = self.create_button();
        button.render();
    }

    fn refresh(&self) {
        println!("Dialog - Refresh");
    }
}


工廠方法的兩個實現之一:Windows風格的按鈕:windows_gui.rs

use crate::gui::{Button, Dialog};

pub struct WindowsButton;

impl Button for WindowsButton {
    fn render(&self) {
        println!("Drawing a Windows button");
        self.on_click();
    }

    fn on_click(&self) {
        println!("Click! Hello, Windows!");
    }
}

pub struct WindowsDialog;

impl Dialog for WindowsDialog {
    /// 建立一個windows風格按鈕
    fn create_button(&self) -> Box<dyn Button> {
        Box::new(WindowsButton)
    }
}


工廠方法的兩個實現之一:html_gui.rs

use crate::gui::{Button, Dialog};

pub struct HtmlButton;

impl Button for HtmlButton {
    fn render(&self) {
        println!("<button>Test Button</button>");
        self.on_click();
    }

    fn on_click(&self) {
        println!("Click! Button says - 'Hello World!'");
    }
}

pub struct HtmlDialog;

impl Dialog for HtmlDialog {
    /// 建立一個 HTML 按鈕.
    fn create_button(&self) -> Box<dyn Button> {
        Box::new(HtmlButton)
    }
}


工廠方法的呼叫客戶端:main.rs

mod gui;
mod html_gui;
mod init;
mod windows_gui;

use init::initialize;

fn main() {
   // 其餘的程式碼並不依賴於特定的對話方塊型別,因為
    // 它透過抽象的`Dialog`特性與所有對話方塊物件一起工作。
    // 在`gui`模組中定義的抽象的`Dialog`特性對所有的對話方塊都有效。
    let dialog = initialize();
    dialog.render();
    dialog.refresh();
}



案例二:遊戲案例:
遊戲房間主介面:game.rs

/// 將用工廠方法例項化的迷宮房間。
pub trait Room {
    fn render(&self);
}

/// 迷宮遊戲有一個工廠方法產生不同的房間。
pub trait MazeGame {
    type RoomImpl:Room;

    /// 一個工廠方法
    fn rooms(&self) -> Vec<Self::RoomImpl>;

    fn play(&self) {
        for room in self.rooms() {
            room.render()。
        }
    }
}

/// 客戶端程式碼初始化資源並做其他準備工作。
/// 然後它使用一個工廠來構建和執行遊戲。
pub fn run(maze_game:impl MazeGame) {
    println! ("載入資源...")。
    println!("開始遊戲...")。

    maze_game.play()。
}


遊戲房間有兩個實現:
  1. 普通遊戲房間:ordinary_maze.rs
  2. 魔法遊戲房間:magic_maze.rs


呼叫工廠方法的客戶端程式碼:main.rs

mod game;
mod magic_maze;
mod ordinary_maze;

use magic_maze::MagicMaze;
use ordinary_maze::OrdinaryMaze;

///遊戲執行時,根據具體的工廠型別,會有不同的迷宮。
///要麼是普通迷宮,要麼是魔法迷宮。
///
/// 為了演示的目的,兩種迷宮都用來構建遊戲。
fn main() {
     // 選項1:遊戲從一個普通迷宮開始。
    let ordinary_maze = OrdinaryMaze::new();
    game::run(ordinary_maze);

     // 選項2:遊戲從一個魔法迷宮開始。
    let magic_maze = MagicMaze::new();
    game::run(magic_maze);
}