diff --git a/Cargo.toml b/Cargo.toml index 4a75c73..fed9697 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,8 @@ members = [ "behavioral/strategy", "behavioral/template-method", "behavioral/visitor", - "creational/abstract-factory", + "creational/abstract-factory/app", + "creational/abstract-factory/app-dyn", "creational/builder", "creational/factory-method/maze-game", "creational/factory-method/render-dialog", diff --git a/creational/abstract-factory/Cargo.toml b/creational/abstract-factory/Cargo.toml deleted file mode 100644 index 619614a..0000000 --- a/creational/abstract-factory/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -edition = "2021" -name = "abstract_factory" -version = "0.1.0" - -[[bin]] -name = "abstract-factory" -path = "main.rs" - -[[bin]] -name = "abstract-factory-dyn" -path = "main_dyn.rs" diff --git a/creational/abstract-factory/README.md b/creational/abstract-factory/README.md index fb75025..81d4879 100644 --- a/creational/abstract-factory/README.md +++ b/creational/abstract-factory/README.md @@ -5,18 +5,40 @@ without specifying their concrete classes._ ## GUI Factory Example -There is a GUI Factory trait and two implementations: Windows and Mac OS factories. A factory can create buttons and checkboxes, however, depending on -thr factory subtype, it will create either Windows or Mac OS components. +This example shows a GUI framework can organize its classes into independent +libraries: + +1. The `gui` library defines interfaces for all the components. + It has no external dependencies. +2. The `windows-gui` library provides Windows implementation of the base GUI. + Depends on `gui`. +3. The `macos-gui` library provides Mac OS implementation of the base GUI. + Depends on `gui`. + +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 the client code works with GUI elements through abstract +interfaces (traits) defined by the `gui` lib. There are also 2 approaches to implementing abstract factory in Rust: -using generics and using dynamic allocation. +using generics (_static dispatch_) and using dynamic allocation +(_dynamic dispatch_). -## `main.rs` +## `app/main.rs` Here, abstract factory is implemented via **generics** which allow the compiler -to create a code that doesn't require dynamic dispatch in runtime. +to create a code that does NOT require dynamic dispatch in runtime. -See `GuiFactory` in [gui/mod.rs](gui/mod.rs). +```rust +pub trait GuiFactory { + type B: Button; + type C: Checkbox; + + fn create_button(&self) -> Self::B; + fn create_checkbox(&self) -> Self::C; +} +``` ### How to Run @@ -33,12 +55,17 @@ Windows checkbox has switched Windows checkbox has switched ``` -## `main_dyn.rs` +## `app-dyn/main.rs` If a concrete type of abstract factory is not known at the compilation time, then is should be implemented using `Box` pointers. -See See `GuiFactoryDynamic` in [gui/mod.rs](gui/mod.rs). +```rust +pub trait GuiFactoryDynamic { + fn create_button(&self) -> Box; + fn create_checkbox(&self) -> Box; +} +``` ### How to Run diff --git a/creational/abstract-factory/app-dyn/Cargo.toml b/creational/abstract-factory/app-dyn/Cargo.toml new file mode 100644 index 0000000..a564c2d --- /dev/null +++ b/creational/abstract-factory/app-dyn/Cargo.toml @@ -0,0 +1,13 @@ +[package] +edition = "2021" +name = "abstract_factory-dyn" +version = "0.1.0" + +[[bin]] +name = "abstract-factory-dyn" +path = "main.rs" + +[dependencies] +gui = {path = "../gui", version = "0.1.0"} +macos-gui = {path = "../macos-gui", version = "0.1.0"} +windows-gui = {path = "../windows-gui", version = "0.1.0"} diff --git a/creational/abstract-factory/app-dyn/main.rs b/creational/abstract-factory/app-dyn/main.rs new file mode 100644 index 0000000..12ee191 --- /dev/null +++ b/creational/abstract-factory/app-dyn/main.rs @@ -0,0 +1,25 @@ +mod render; + +use render::render; + +use gui::GuiFactoryDynamic; +use macos_gui::factory::MacFactory; +use windows_gui::factory::WindowsFactory; + +fn main() { + let windows = false; + + // Allocate a factory object in runtime depending on unpredictable input. + let factory: &dyn GuiFactoryDynamic = if windows { + &WindowsFactory + } else { + &MacFactory + }; + + // Factory invocation can be inlined right here. + let button = factory.create_button(); + button.press(); + + // Factory object can be passed to a function as a parameter. + render(factory); +} diff --git a/creational/abstract-factory/app-dyn/render.rs b/creational/abstract-factory/app-dyn/render.rs new file mode 100644 index 0000000..e2177a3 --- /dev/null +++ b/creational/abstract-factory/app-dyn/render.rs @@ -0,0 +1,17 @@ +//! The code demonstrates that it doesn't depend on a concrete +//! factory implementation. + +use gui::GuiFactoryDynamic; + +/// Renders GUI. +pub fn render(factory: &dyn GuiFactoryDynamic) { + let button1 = factory.create_button(); + let button2 = factory.create_button(); + let checkbox1 = factory.create_checkbox(); + let checkbox2 = factory.create_checkbox(); + + button1.press(); + button2.press(); + checkbox1.switch(); + checkbox2.switch(); +} diff --git a/creational/abstract-factory/app/Cargo.toml b/creational/abstract-factory/app/Cargo.toml new file mode 100644 index 0000000..78345a5 --- /dev/null +++ b/creational/abstract-factory/app/Cargo.toml @@ -0,0 +1,13 @@ +[package] +edition = "2021" +name = "abstract_factory" +version = "0.1.0" + +[[bin]] +name = "abstract-factory" +path = "main.rs" + +[dependencies] +gui = {path = "../gui", version = "0.1.0"} +macos-gui = {path = "../macos-gui", version = "0.1.0"} +windows-gui = {path = "../windows-gui", version = "0.1.0"} diff --git a/creational/abstract-factory/app/main.rs b/creational/abstract-factory/app/main.rs new file mode 100644 index 0000000..68cd05d --- /dev/null +++ b/creational/abstract-factory/app/main.rs @@ -0,0 +1,16 @@ +mod render; + +use render::render; + +use macos_gui::factory::MacFactory; +use windows_gui::factory::WindowsFactory; + +fn main() { + let windows = true; + + if windows { + render(WindowsFactory); + } else { + render(MacFactory); + } +} diff --git a/creational/abstract-factory/app/render.rs b/creational/abstract-factory/app/render.rs new file mode 100644 index 0000000..b35db9f --- /dev/null +++ b/creational/abstract-factory/app/render.rs @@ -0,0 +1,20 @@ +//! The code demonstrates that it doesn't depend on a concrete +//! factory implementation. + +use gui::GuiFactory; + +// Renders GUI. Factory object must be passed as a parameter to such the +// generic function with factory invocation to utilize static dispatch. +pub fn render(factory: impl GuiFactory) { + let button1 = factory.create_button(); + let button2 = factory.create_button(); + let checkbox1 = factory.create_checkbox(); + let checkbox2 = factory.create_checkbox(); + + use gui::{Button, Checkbox}; + + button1.press(); + button2.press(); + checkbox1.switch(); + checkbox2.switch(); +} diff --git a/creational/abstract-factory/gui/Cargo.toml b/creational/abstract-factory/gui/Cargo.toml new file mode 100644 index 0000000..ff407c4 --- /dev/null +++ b/creational/abstract-factory/gui/Cargo.toml @@ -0,0 +1,7 @@ +[package] +edition = "2021" +name = "gui" +version = "0.1.0" + +[lib] +path = "lib.rs" diff --git a/creational/abstract-factory/gui/mod.rs b/creational/abstract-factory/gui/lib.rs similarity index 93% rename from creational/abstract-factory/gui/mod.rs rename to creational/abstract-factory/gui/lib.rs index df72dd5..c2ad78a 100644 --- a/creational/abstract-factory/gui/mod.rs +++ b/creational/abstract-factory/gui/lib.rs @@ -1,6 +1,3 @@ -pub mod macos; -pub mod windows; - pub trait Button { fn press(&self); } diff --git a/creational/abstract-factory/macos-gui/Cargo.toml b/creational/abstract-factory/macos-gui/Cargo.toml new file mode 100644 index 0000000..f3123c0 --- /dev/null +++ b/creational/abstract-factory/macos-gui/Cargo.toml @@ -0,0 +1,10 @@ +[package] +edition = "2021" +name = "macos-gui" +version = "0.1.0" + +[lib] +path = "lib.rs" + +[dependencies] +gui = {path = "../gui", version = "0.1.0"} diff --git a/creational/abstract-factory/macos-gui/button.rs b/creational/abstract-factory/macos-gui/button.rs new file mode 100644 index 0000000..38bbe5c --- /dev/null +++ b/creational/abstract-factory/macos-gui/button.rs @@ -0,0 +1,9 @@ +use gui::Button; + +pub struct MacButton; + +impl Button for MacButton { + fn press(&self) { + println!("MacOS button has pressed"); + } +} diff --git a/creational/abstract-factory/macos-gui/checkbox.rs b/creational/abstract-factory/macos-gui/checkbox.rs new file mode 100644 index 0000000..571bc2c --- /dev/null +++ b/creational/abstract-factory/macos-gui/checkbox.rs @@ -0,0 +1,9 @@ +use gui::Checkbox; + +pub struct MacCheckbox; + +impl Checkbox for MacCheckbox { + fn switch(&self) { + println!("MacOS checkbox has switched"); + } +} diff --git a/creational/abstract-factory/gui/macos.rs b/creational/abstract-factory/macos-gui/factory.rs similarity index 59% rename from creational/abstract-factory/gui/macos.rs rename to creational/abstract-factory/macos-gui/factory.rs index d7bfc1a..944abbe 100644 --- a/creational/abstract-factory/gui/macos.rs +++ b/creational/abstract-factory/macos-gui/factory.rs @@ -1,20 +1,8 @@ -use super::{Button, Checkbox, GuiFactory, GuiFactoryDynamic}; +use gui::{Button, Checkbox, GuiFactory, GuiFactoryDynamic}; -pub struct MacButton; -pub struct MacCheckbox; -pub struct MacFactory; - -impl Button for MacButton { - fn press(&self) { - println!("MacOS button has pressed"); - } -} +use crate::{button::MacButton, checkbox::MacCheckbox}; -impl Checkbox for MacCheckbox { - fn switch(&self) { - println!("MacOS checkbox has switched"); - } -} +pub struct MacFactory; impl GuiFactory for MacFactory { type B = MacButton; diff --git a/creational/abstract-factory/macos-gui/lib.rs b/creational/abstract-factory/macos-gui/lib.rs new file mode 100644 index 0000000..02511e0 --- /dev/null +++ b/creational/abstract-factory/macos-gui/lib.rs @@ -0,0 +1,3 @@ +pub mod button; +pub mod checkbox; +pub mod factory; diff --git a/creational/abstract-factory/main.rs b/creational/abstract-factory/main.rs deleted file mode 100644 index a47776d..0000000 --- a/creational/abstract-factory/main.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Generics allow the compiler to create a code that doesn't require -//! dynamic dispatch in runtime aka virtual method invocation. - -mod gui; - -// Factory object should be passed as a parameter to a generic function -// with a client code that contains factory invocation. -fn render(factory: impl gui::GuiFactory) { - let button1 = factory.create_button(); - let button2 = factory.create_button(); - let checkbox1 = factory.create_checkbox(); - let checkbox2 = factory.create_checkbox(); - - use gui::{Button, Checkbox}; - - button1.press(); - button2.press(); - checkbox1.switch(); - checkbox2.switch(); -} - -fn main() { - use gui::{macos::MacFactory, windows::WindowsFactory}; - - let windows = true; - - if windows { - render(WindowsFactory); - } else { - render(MacFactory); - } -} diff --git a/creational/abstract-factory/main_dyn.rs b/creational/abstract-factory/main_dyn.rs deleted file mode 100644 index db3d8cb..0000000 --- a/creational/abstract-factory/main_dyn.rs +++ /dev/null @@ -1,39 +0,0 @@ -//! Dynamic dispatch affects performance in runtime, thus, you might -//! prefer implementation via generics to make the compiler figure out a proper -//! factory type. - -mod gui; - -/// The client code calls the creation methods of a factory object instead of -/// creating products directly with a constructor call. -fn render(factory: &dyn gui::GuiFactoryDynamic) { - let button1 = factory.create_button(); - let button2 = factory.create_button(); - let checkbox1 = factory.create_checkbox(); - let checkbox2 = factory.create_checkbox(); - - button1.press(); - button2.press(); - checkbox1.switch(); - checkbox2.switch(); -} - -fn main() { - use gui::{macos::MacFactory, windows::WindowsFactory}; - - let windows = false; - - // Allocate a factory object in runtime depending on unpredictable input. - let factory: &dyn gui::GuiFactoryDynamic = if windows { - &WindowsFactory - } else { - &MacFactory - }; - - // Factory invocation can be inlined right here then. - let button = factory.create_button(); - button.press(); - - // Factory object can be passed to a function as a parameter. - render(factory); -} diff --git a/creational/abstract-factory/windows-gui/Cargo.toml b/creational/abstract-factory/windows-gui/Cargo.toml new file mode 100644 index 0000000..bb94b4a --- /dev/null +++ b/creational/abstract-factory/windows-gui/Cargo.toml @@ -0,0 +1,10 @@ +[package] +edition = "2021" +name = "windows-gui" +version = "0.1.0" + +[lib] +path = "lib.rs" + +[dependencies] +gui = {path = "../gui", version = "0.1.0"} diff --git a/creational/abstract-factory/windows-gui/button.rs b/creational/abstract-factory/windows-gui/button.rs new file mode 100644 index 0000000..6a041e6 --- /dev/null +++ b/creational/abstract-factory/windows-gui/button.rs @@ -0,0 +1,9 @@ +use gui::Button; + +pub struct WindowsButton; + +impl Button for WindowsButton { + fn press(&self) { + println!("Windows button has pressed"); + } +} diff --git a/creational/abstract-factory/windows-gui/checkbox.rs b/creational/abstract-factory/windows-gui/checkbox.rs new file mode 100644 index 0000000..5eee67b --- /dev/null +++ b/creational/abstract-factory/windows-gui/checkbox.rs @@ -0,0 +1,9 @@ +use gui::Checkbox; + +pub struct WindowsCheckbox; + +impl Checkbox for WindowsCheckbox { + fn switch(&self) { + println!("Windows checkbox has switched"); + } +} diff --git a/creational/abstract-factory/gui/windows.rs b/creational/abstract-factory/windows-gui/factory.rs similarity index 59% rename from creational/abstract-factory/gui/windows.rs rename to creational/abstract-factory/windows-gui/factory.rs index 1da7508..4c5ace5 100644 --- a/creational/abstract-factory/gui/windows.rs +++ b/creational/abstract-factory/windows-gui/factory.rs @@ -1,20 +1,8 @@ -use super::{Button, Checkbox, GuiFactory, GuiFactoryDynamic}; +use gui::{Button, Checkbox, GuiFactory, GuiFactoryDynamic}; -pub struct WindowsButton; -pub struct WindowsCheckbox; -pub struct WindowsFactory; - -impl Button for WindowsButton { - fn press(&self) { - println!("Windows button has pressed"); - } -} +use crate::{button::WindowsButton, checkbox::WindowsCheckbox}; -impl Checkbox for WindowsCheckbox { - fn switch(&self) { - println!("Windows checkbox has switched"); - } -} +pub struct WindowsFactory; impl GuiFactory for WindowsFactory { type B = WindowsButton; diff --git a/creational/abstract-factory/windows-gui/lib.rs b/creational/abstract-factory/windows-gui/lib.rs new file mode 100644 index 0000000..02511e0 --- /dev/null +++ b/creational/abstract-factory/windows-gui/lib.rs @@ -0,0 +1,3 @@ +pub mod button; +pub mod checkbox; +pub mod factory;