Rust语言之GoF设计模式:工厂模式


工厂模式的是将创建逻辑封装在一个方法中,在 "外部"实现对其使用。
(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);
}