Skip to content

Commit a7a76b6

Browse files
authored
Provide an alternative structure of Factory Method examples (#3)
Decouple traits and implementations. The aim is to demonstrate a value of the Factory Method by decoupling interfaces and implementations. Although, the full decoupling is going to be demonstrated in the Abstract Factory example.
1 parent a0daff7 commit a7a76b6

File tree

14 files changed

+121
-98
lines changed

14 files changed

+121
-98
lines changed

creational/factory-method/README.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ of objects that will be created._
66

77
## Maze Game
88

9-
Implementing the Factory Method pattern using **static dispatch** (generics).
10-
119
This example reproduces one from the GoF Design Patterns book:
12-
https://en.wikipedia.org/wiki/Factory_method_pattern
10+
https://en.wikipedia.org/wiki/Factory_method_pattern, implementing
11+
the Factory Method pattern using generics (_static dispatch_).
1312

1413
### How to Run
1514

@@ -32,10 +31,24 @@ Ordinary Room: #1
3231

3332
## Render Dialog
3433

35-
Implementing the Factory Method via **dynamic dispatch**.
34+
This example shows a GUI framework can organize its types into
35+
independent modules:
36+
37+
1. The `gui` module defines interfaces for all the components.
38+
It has no external dependencies.
39+
2. The `html_gui` module provides HTML implementation of the base GUI.
40+
Depends on `gui`.
41+
3. The `windows_gui` module provides Windows implementation of the base GUI.
42+
Depends on `gui`.
43+
44+
The app is a client application that can use several implementations
45+
of the GUI framework, depending on the current environment or configuration.
46+
However, most of the app code doesn't depend on specific types of GUI elements.
47+
All the client code works with GUI elements through abstract interfaces
48+
defined by the `gui` module.
3649

37-
See [Factory Method Java Example](https://refactoring.guru/design-patterns/factory-method/java/example)
38-
as a reference.
50+
The [Abstract Factory example](../abstract-factory/) demonstrates even greater
51+
separation of Factory interface and its implementations.
3952

4053
### How to Run
4154

@@ -46,7 +59,12 @@ cargo run --bin factory-method-render-dialog
4659
### Output
4760

4861
```
62+
-- No OS detected, creating the HTML GUI --
4963
<button>Test Button</button>
5064
Click! Button says - 'Hello World!'
5165
Dialog - Refresh
5266
```
67+
68+
### Reference
69+
70+
This example reproduces [Factory Method Java Example](https://refactoring.guru/design-patterns/factory-method/java/example).
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/// Maze room that is going to be instantiated with a factory method.
2+
pub trait Room {
3+
fn render(&self);
4+
}
5+
6+
/// Maze game has a factory method producing different rooms.
7+
pub trait MazeGame {
8+
type RoomImpl: Room;
9+
10+
/// A factory method.
11+
fn rooms(&self) -> Vec<Self::RoomImpl>;
12+
13+
fn play(&self) {
14+
for room in self.rooms() {
15+
room.render();
16+
}
17+
}
18+
}
19+
20+
/// The client code initializes resources and does other preparations
21+
/// then it uses a factory to construct and run the game.
22+
pub fn run(maze_game: impl MazeGame) {
23+
println!("Loading resources...");
24+
println!("Starting the game...");
25+
26+
maze_game.play();
27+
}

creational/factory-method/maze-game/game/mod.rs

Lines changed: 0 additions & 23 deletions
This file was deleted.

creational/factory-method/maze-game/game/magic_maze.rs renamed to creational/factory-method/maze-game/magic_maze.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{MazeGame, Room};
1+
use super::game::{MazeGame, Room};
22

33
#[derive(Clone)]
44
pub struct MagicRoom {
@@ -17,11 +17,11 @@ impl Room for MagicRoom {
1717
}
1818
}
1919

20-
pub struct MagicMazeGame {
20+
pub struct MagicMaze {
2121
rooms: Vec<MagicRoom>,
2222
}
2323

24-
impl MagicMazeGame {
24+
impl MagicMaze {
2525
pub fn new() -> Self {
2626
Self {
2727
rooms: vec![
@@ -32,7 +32,7 @@ impl MagicMazeGame {
3232
}
3333
}
3434

35-
impl MazeGame for MagicMazeGame {
35+
impl MazeGame for MagicMaze {
3636
type RoomImpl = MagicRoom;
3737

3838
fn rooms(&self) -> Vec<Self::RoomImpl> {
Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
mod game;
2+
mod magic_maze;
3+
mod ordinary_maze;
24

3-
/// The client code demonstrates that it does its own things
4-
/// (e.g. loading resources) and it can use a factory in a proper place to
5-
/// construct a game.
6-
fn run(game: impl game::MazeGame) {
7-
println!("Loading resources...");
8-
println!("Starting the game...");
9-
10-
game.play();
11-
}
5+
use magic_maze::MagicMaze;
6+
use ordinary_maze::OrdinaryMaze;
127

8+
/// The game runs with different mazes depending on the concrete factory type:
9+
/// it's either an ordinary maze or a magic maze.
10+
///
11+
/// For demonstration purposes, both mazes are used to construct the game.
1312
fn main() {
14-
let game = game::MagicMazeGame::new();
15-
run(game);
13+
// Option 1: The game starts with an ordinary maze.
14+
let ordinary_maze = OrdinaryMaze::new();
15+
game::run(ordinary_maze);
1616

17-
let game = game::OrdinaryMazeGame::new();
18-
run(game);
17+
// Option 2: The game starts with a magic maze.
18+
let magic_maze = MagicMaze::new();
19+
game::run(magic_maze);
1920
}

creational/factory-method/maze-game/game/ordinary_maze.rs renamed to creational/factory-method/maze-game/ordinary_maze.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{MazeGame, Room};
1+
use super::game::{MazeGame, Room};
22

33
#[derive(Clone)]
44
pub struct OrdinaryRoom {
@@ -17,19 +17,19 @@ impl Room for OrdinaryRoom {
1717
}
1818
}
1919

20-
pub struct OrdinaryMazeGame {
20+
pub struct OrdinaryMaze {
2121
rooms: Vec<OrdinaryRoom>,
2222
}
2323

24-
impl OrdinaryMazeGame {
24+
impl OrdinaryMaze {
2525
pub fn new() -> Self {
2626
Self {
2727
rooms: vec![OrdinaryRoom::new(1), OrdinaryRoom::new(2)],
2828
}
2929
}
3030
}
3131

32-
impl MazeGame for OrdinaryMazeGame {
32+
impl MazeGame for OrdinaryMaze {
3333
type RoomImpl = OrdinaryRoom;
3434

3535
fn rooms(&self) -> Vec<Self::RoomImpl> {

creational/factory-method/render-dialog/button/mod.rs

Lines changed: 0 additions & 7 deletions
This file was deleted.

creational/factory-method/render-dialog/dialog/html.rs

Lines changed: 0 additions & 11 deletions
This file was deleted.

creational/factory-method/render-dialog/dialog/windows.rs

Lines changed: 0 additions & 11 deletions
This file was deleted.

creational/factory-method/render-dialog/dialog/mod.rs renamed to creational/factory-method/render-dialog/gui.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
pub mod html;
2-
pub mod windows;
3-
4-
use crate::button::Button;
1+
pub trait Button {
2+
fn render(&self);
3+
fn on_click(&self);
4+
}
55

66
/// Dialog has a factory method `create_button`.
77
///
88
/// It creates different buttons depending on a factory implementation.
99
pub trait Dialog {
10+
/// The factory method. It must be overridden with a concrete implementation.
1011
fn create_button(&self) -> Box<dyn Button>;
1112

1213
fn render(&self) {

0 commit comments

Comments
 (0)