Skip to content

Commit a8e3776

Browse files
committed
Abstract Factory: full decoupling of traits and implementations
1 parent 92f9a62 commit a8e3776

File tree

23 files changed

+216
-125
lines changed

23 files changed

+216
-125
lines changed

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ members = [
1111
"behavioral/strategy",
1212
"behavioral/template-method",
1313
"behavioral/visitor",
14-
"creational/abstract-factory",
14+
"creational/abstract-factory/app",
15+
"creational/abstract-factory/app-dyn",
1516
"creational/builder",
1617
"creational/factory-method/maze-game",
1718
"creational/factory-method/render-dialog",

creational/abstract-factory/Cargo.toml

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

creational/abstract-factory/README.md

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,40 @@ without specifying their concrete classes._
55

66
## GUI Factory Example
77

8-
There is a GUI Factory trait and two implementations: Windows and Mac OS factories. A factory can create buttons and checkboxes, however, depending on
9-
thr factory subtype, it will create either Windows or Mac OS components.
8+
This example shows a GUI framework can organize its classes into independent
9+
libraries:
10+
11+
1. The `gui` library defines interfaces for all the components.
12+
It has no external dependencies.
13+
2. The `windows` library provides Windows implementation of the base GUI.
14+
Depends on `gui`.
15+
3. The `macos` library provides Mac OS implementation of the base GUI.
16+
Depends on `gui`.
17+
18+
The `app` is a client application that can use several implementations of the
19+
GUI framework, depending on the current environment or configuration.
20+
However, most of the `app` code _**doesn't depend on specific types of GUI
21+
elements**_. All the client code works with GUI elements through abstract
22+
interfaces (traits) defined by the `gui` lib.
1023

1124
There are also 2 approaches to implementing abstract factory in Rust:
12-
using generics and using dynamic allocation.
25+
using generics (_static dispatch_) and using dynamic allocation
26+
(_dynamic dispatch_).
1327

14-
## `main.rs`
28+
## `app/main.rs`
1529

1630
Here, abstract factory is implemented via **generics** which allow the compiler
17-
to create a code that doesn't require dynamic dispatch in runtime.
31+
to create a code that does NOT require dynamic dispatch in runtime.
1832

19-
See `GuiFactory` in [gui/mod.rs](gui/mod.rs).
33+
```rust
34+
pub trait GuiFactory {
35+
type B: Button;
36+
type C: Checkbox;
37+
38+
fn create_button(&self) -> Self::B;
39+
fn create_checkbox(&self) -> Self::C;
40+
}
41+
```
2042

2143
### How to Run
2244

@@ -33,12 +55,17 @@ Windows checkbox has switched
3355
Windows checkbox has switched
3456
```
3557

36-
## `main_dyn.rs`
58+
## `app/main_dyn.rs`
3759

3860
If a concrete type of abstract factory is not known at the compilation time,
3961
then is should be implemented using `Box` pointers.
4062

41-
See See `GuiFactoryDynamic` in [gui/mod.rs](gui/mod.rs).
63+
```rust
64+
pub trait GuiFactoryDynamic {
65+
fn create_button(&self) -> Box<dyn Button>;
66+
fn create_checkbox(&self) -> Box<dyn Checkbox>;
67+
}
68+
```
4269

4370
### How to Run
4471

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
edition = "2021"
3+
name = "abstract_factory-dyn"
4+
version = "0.1.0"
5+
6+
[[bin]]
7+
name = "abstract-factory-dyn"
8+
path = "main.rs"
9+
10+
[dependencies]
11+
gui = {path = "../gui", version = "0.1.0"}
12+
macos-gui = {path = "../macos-gui", version = "0.1.0"}
13+
windows-gui = {path = "../windows-gui", version = "0.1.0"}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
mod render;
2+
3+
use render::render;
4+
5+
use gui::GuiFactoryDynamic;
6+
use macos_gui::factory::MacFactory;
7+
use windows_gui::factory::WindowsFactory;
8+
9+
fn main() {
10+
let windows = false;
11+
12+
// Allocate a factory object in runtime depending on unpredictable input.
13+
let factory: &dyn GuiFactoryDynamic = if windows {
14+
&WindowsFactory
15+
} else {
16+
&MacFactory
17+
};
18+
19+
// Factory invocation can be inlined right here.
20+
let button = factory.create_button();
21+
button.press();
22+
23+
// Factory object can be passed to a function as a parameter.
24+
render(factory);
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//! The code demonstrates that it doesn't depend on a concrete
2+
//! factory implementation.
3+
4+
use gui::GuiFactoryDynamic;
5+
6+
/// Renders GUI.
7+
pub fn render(factory: &dyn GuiFactoryDynamic) {
8+
let button1 = factory.create_button();
9+
let button2 = factory.create_button();
10+
let checkbox1 = factory.create_checkbox();
11+
let checkbox2 = factory.create_checkbox();
12+
13+
button1.press();
14+
button2.press();
15+
checkbox1.switch();
16+
checkbox2.switch();
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
edition = "2021"
3+
name = "abstract_factory"
4+
version = "0.1.0"
5+
6+
[[bin]]
7+
name = "abstract-factory"
8+
path = "main.rs"
9+
10+
[dependencies]
11+
gui = {path = "../gui", version = "0.1.0"}
12+
macos-gui = {path = "../macos-gui", version = "0.1.0"}
13+
windows-gui = {path = "../windows-gui", version = "0.1.0"}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
mod render;
2+
3+
use render::render;
4+
5+
use macos_gui::factory::MacFactory;
6+
use windows_gui::factory::WindowsFactory;
7+
8+
fn main() {
9+
let windows = true;
10+
11+
if windows {
12+
render(WindowsFactory);
13+
} else {
14+
render(MacFactory);
15+
}
16+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//! The code demonstrates that it doesn't depend on a concrete
2+
//! factory implementation.
3+
4+
use gui::GuiFactory;
5+
6+
// Renders GUI. Factory object must be passed as a parameter to such the
7+
// generic function with factory invocation to utilize static dispatch.
8+
pub fn render(factory: impl GuiFactory) {
9+
let button1 = factory.create_button();
10+
let button2 = factory.create_button();
11+
let checkbox1 = factory.create_checkbox();
12+
let checkbox2 = factory.create_checkbox();
13+
14+
use gui::{Button, Checkbox};
15+
16+
button1.press();
17+
button2.press();
18+
checkbox1.switch();
19+
checkbox2.switch();
20+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[package]
2+
edition = "2021"
3+
name = "gui"
4+
version = "0.1.0"
5+
6+
[lib]
7+
path = "lib.rs"

0 commit comments

Comments
 (0)