
Rust 工厂方法模式讲解和代码示例
工厂方法是一种创建型设计模式, 解决了在不指定具体类的情况下创建产品对象的问题。
工厂方法定义了一个方法, 且必须使用该方法代替通过直接调用构造函数来创建对象 ( new
操作符) 的方式。 子类可重写该方法来更改将被创建的对象所属类。
如果你不清楚工厂、 工厂方法和抽象工厂模式之间的区别, 请参阅工厂模式比较。
Dialog Rendering
This example illustrates how to organize a GUI framework into independent modules using dynamic dispatch:
- The
gui
module defines interfaces for all the components.
It has no external dependencies. - The
html_gui
module provides HTML implementation of the base GUI.
Depends ongui
. - The
windows_gui
module provides Windows implementation of the base GUI.
Depends ongui
.
The app
is a client application that can use several implementations of the GUI framework, depending on the current environment or configuration. However, most of the app code doesn't depend on specific types of GUI elements. All client code works with GUI elements through abstract interfaces defined by the gui
module.
The Abstract Factory example demonstrates an even greater separation of a factory interface and its implementations.
gui.rs: Product & Creator
pub trait Button {
fn render(&self);
fn on_click(&self);
}
/// Dialog has a factory method `create_button`.
///
/// It creates different buttons depending on a factory implementation.
pub trait Dialog {
/// The factory method. It must be overridden with a concrete implementation.
fn create_button(&self) -> Box<dyn Button>;
fn render(&self) {
let button = self.create_button();
button.render();
}
fn refresh(&self) {
println!("Dialog - Refresh");
}
}
html_gui.rs: Concrete creator
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 {
/// Creates an HTML button.
fn create_button(&self) -> Box<dyn Button> {
Box::new(HtmlButton)
}
}
windows_gui.rs: Another concrete creator
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 {
/// Creates a Windows button.
fn create_button(&self) -> Box<dyn Button> {
Box::new(WindowsButton)
}
}
init.rs: Initialization code
use crate::gui::Dialog;
use crate::html_gui::HtmlDialog;
use crate::windows_gui::WindowsDialog;
pub fn initialize() -> &'static dyn Dialog {
// The dialog type is selected depending on the environment settings or configuration.
if cfg!(windows) {
println!("-- Windows detected, creating Windows GUI --");
&WindowsDialog
} else {
println!("-- No OS detected, creating the HTML GUI --");
&HtmlDialog
}
}
main.rs: Client code
mod gui;
mod html_gui;
mod init;
mod windows_gui;
use init::initialize;
fn main() {
// The rest of the code doesn't depend on specific dialog types, because
// it works with all dialog objects via the abstract `Dialog` trait
// which is defined in the `gui` module.
let dialog = initialize();
dialog.render();
dialog.refresh();
}
Output
<button>Test Button</button> Click! Button says - 'Hello World!' Dialog - Refresh
Maze Game
This example illustrates how to implement the Factory Method pattern using static dispatch (generics).
Inspired by the Factory Method example from the GoF book.
game.rs
/// Maze room that is going to be instantiated with a factory method.
pub trait Room {
fn render(&self);
}
/// Maze game has a factory method producing different rooms.
pub trait MazeGame {
type RoomImpl: Room;
/// A factory method.
fn rooms(&self) -> Vec<Self::RoomImpl>;
fn play(&self) {
for room in self.rooms() {
room.render();
}
}
}
/// The client code initializes resources and does other preparations
/// then it uses a factory to construct and run the game.
pub fn run(maze_game: impl MazeGame) {
println!("Loading resources...");
println!("Starting the game...");
maze_game.play();
}
magic_maze.rs
use super::game::{MazeGame, Room};
#[derive(Clone)]
pub struct MagicRoom {
title: String,
}
impl MagicRoom {
pub fn new(title: String) -> Self {
Self { title }
}
}
impl Room for MagicRoom {
fn render(&self) {
println!("Magic Room: {}", self.title);
}
}
pub struct MagicMaze {
rooms: Vec<MagicRoom>,
}
impl MagicMaze {
pub fn new() -> Self {
Self {
rooms: vec![
MagicRoom::new("Infinite Room".into()),
MagicRoom::new("Red Room".into()),
],
}
}
}
impl MazeGame for MagicMaze {
type RoomImpl = MagicRoom;
fn rooms(&self) -> Vec<Self::RoomImpl> {
self.rooms.clone()
}
}
ordinary_maze.rs
use super::game::{MazeGame, Room};
#[derive(Clone)]
pub struct OrdinaryRoom {
id: u32,
}
impl OrdinaryRoom {
pub fn new(id: u32) -> Self {
Self { id }
}
}
impl Room for OrdinaryRoom {
fn render(&self) {
println!("Ordinary Room: #{}", self.id);
}
}
pub struct OrdinaryMaze {
rooms: Vec<OrdinaryRoom>,
}
impl OrdinaryMaze {
pub fn new() -> Self {
Self {
rooms: vec![OrdinaryRoom::new(1), OrdinaryRoom::new(2)],
}
}
}
impl MazeGame for OrdinaryMaze {
type RoomImpl = OrdinaryRoom;
fn rooms(&self) -> Vec<Self::RoomImpl> {
let mut rooms = self.rooms.clone();
rooms.reverse();
rooms
}
}
main.rs: Client code
mod game;
mod magic_maze;
mod ordinary_maze;
use magic_maze::MagicMaze;
use ordinary_maze::OrdinaryMaze;
/// The game runs with different mazes depending on the concrete factory type:
/// it's either an ordinary maze or a magic maze.
///
/// For demonstration purposes, both mazes are used to construct the game.
fn main() {
// Option 1: The game starts with an ordinary maze.
let ordinary_maze = OrdinaryMaze::new();
game::run(ordinary_maze);
// Option 2: The game starts with a magic maze.
let magic_maze = MagicMaze::new();
game::run(magic_maze);
}
Output
Loading resources... Starting the game... Magic Room: Infinite Room Magic Room: Red Room Loading resources... Starting the game... Ordinary Room: #2 Ordinary Room: #1