diff --git a/Cargo.toml b/Cargo.toml index 4a75c73..9b98326 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "creational/builder", "creational/factory-method/maze-game", "creational/factory-method/render-dialog", + "creational/factory-method/render-dialog-alternative/app", "creational/prototype", "creational/simple-factory", "creational/singleton", diff --git a/creational/factory-method/README.md b/creational/factory-method/README.md index d9c0d6c..c403700 100644 --- a/creational/factory-method/README.md +++ b/creational/factory-method/README.md @@ -32,10 +32,13 @@ Ordinary Room: #1 ## Render Dialog -Implementing the Factory Method via **dynamic dispatch**. +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 `html_gui` library provides HTML implementation of the base GUI. Depends on `gui`. +3. The `windows_gui` library provides Windows implementation of the base GUI. Depends on `gui`. -See [Factory Method Java Example](https://refactoring.guru/design-patterns/factory-method/java/example) -as a reference. +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 defined by the `gui` lib. ### How to Run diff --git a/creational/factory-method/render-dialog-alternative/app/Cargo.toml b/creational/factory-method/render-dialog-alternative/app/Cargo.toml new file mode 100644 index 0000000..0105285 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/app/Cargo.toml @@ -0,0 +1,13 @@ +[package] +edition = "2021" +name = "render-dialog-alternative" +version = "1.0.0" + +[[bin]] +name = "factory-method-render-dialog" +path = "main.rs" + +[dependencies] +gui = { path = "../gui", version = "1.0" } +html_gui = { path = "../html_gui", version = "1.0" } +windows_gui = { path = "../windows_gui", version = "1.0" } diff --git a/creational/factory-method/render-dialog-alternative/app/init.rs b/creational/factory-method/render-dialog-alternative/app/init.rs new file mode 100644 index 0000000..92683ef --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/app/init.rs @@ -0,0 +1,14 @@ +use gui::dialog::Dialog; +use html_gui::dialog::HtmlDialog; +use windows_gui::dialog::WindowsDialog; + +pub fn initialize() -> Box { + // The dialog type is selected depending on the environment settings or configuration. + if cfg!(windows) { + println!("-- Windows detected, creating Windows GUI --"); + return Box::new(WindowsDialog) + } else { + println!("-- No OS detected, creating the HTML GUI --"); + return Box::new(HtmlDialog) + }; +} \ No newline at end of file diff --git a/creational/factory-method/render-dialog-alternative/app/main.rs b/creational/factory-method/render-dialog-alternative/app/main.rs new file mode 100644 index 0000000..5e59062 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/app/main.rs @@ -0,0 +1,12 @@ +use gui::dialog::Dialog; + +mod init; +use crate::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. + let dialog: Box = initialize(); + dialog.render(); + dialog.refresh(); +} diff --git a/creational/factory-method/render-dialog-alternative/gui/Cargo.toml b/creational/factory-method/render-dialog-alternative/gui/Cargo.toml new file mode 100644 index 0000000..e1771d3 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/gui/Cargo.toml @@ -0,0 +1,7 @@ +[package] +edition = "2021" +name = "gui" +version = "1.0.0" + +[lib] +path = "lib.rs" diff --git a/creational/factory-method/render-dialog-alternative/gui/button.rs b/creational/factory-method/render-dialog-alternative/gui/button.rs new file mode 100644 index 0000000..27f74cd --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/gui/button.rs @@ -0,0 +1,4 @@ +pub trait Button { + fn render(&self); + fn on_click(&self); +} \ No newline at end of file diff --git a/creational/factory-method/render-dialog-alternative/gui/dialog.rs b/creational/factory-method/render-dialog-alternative/gui/dialog.rs new file mode 100644 index 0000000..79b5b3c --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/gui/dialog.rs @@ -0,0 +1,18 @@ +use crate::button::Button; + +/// Dialog has a factory method `create_button`. +/// +/// It creates different buttons depending on a factory implementation. +pub trait Dialog { + fn create_button(&self) -> Box; + + fn render(&self) { + println!("Dialog: Using base render method."); + let button = self.create_button(); + button.render(); + } + + fn refresh(&self) { + println!("Dialog: Using base refresh method."); + } +} \ No newline at end of file diff --git a/creational/factory-method/render-dialog-alternative/gui/lib.rs b/creational/factory-method/render-dialog-alternative/gui/lib.rs new file mode 100644 index 0000000..4f4e867 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/gui/lib.rs @@ -0,0 +1,2 @@ +pub mod button; +pub mod dialog; \ No newline at end of file diff --git a/creational/factory-method/render-dialog-alternative/html_gui/Cargo.toml b/creational/factory-method/render-dialog-alternative/html_gui/Cargo.toml new file mode 100644 index 0000000..5f6e4e1 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/html_gui/Cargo.toml @@ -0,0 +1,10 @@ +[package] +edition = "2021" +name = "html_gui" +version = "1.0.0" + +[lib] +path = "lib.rs" + +[dependencies] +gui = { path = "../gui", version = "1.0" } diff --git a/creational/factory-method/render-dialog-alternative/html_gui/button.rs b/creational/factory-method/render-dialog-alternative/html_gui/button.rs new file mode 100644 index 0000000..c8d8e6a --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/html_gui/button.rs @@ -0,0 +1,14 @@ +use gui::button::Button; + +pub struct HtmlButton; + +impl Button for HtmlButton { + fn render(&self) { + println!(""); + self.on_click(); + } + + fn on_click(&self) { + println!("Button: I'm an HTML button!"); + } +} diff --git a/creational/factory-method/render-dialog-alternative/html_gui/dialog.rs b/creational/factory-method/render-dialog-alternative/html_gui/dialog.rs new file mode 100644 index 0000000..b22e687 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/html_gui/dialog.rs @@ -0,0 +1,12 @@ +use gui::button::Button; +use gui::dialog::Dialog; + +use crate::button::HtmlButton; + +pub struct HtmlDialog; + +impl Dialog for HtmlDialog { + fn create_button(&self) -> Box { + Box::new(HtmlButton) + } +} diff --git a/creational/factory-method/render-dialog-alternative/html_gui/lib.rs b/creational/factory-method/render-dialog-alternative/html_gui/lib.rs new file mode 100644 index 0000000..4f4e867 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/html_gui/lib.rs @@ -0,0 +1,2 @@ +pub mod button; +pub mod dialog; \ No newline at end of file diff --git a/creational/factory-method/render-dialog-alternative/windows_gui/Cargo.toml b/creational/factory-method/render-dialog-alternative/windows_gui/Cargo.toml new file mode 100644 index 0000000..ae64cdf --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/windows_gui/Cargo.toml @@ -0,0 +1,10 @@ +[package] +edition = "2021" +name = "windows_gui" +version = "1.0.0" + +[lib] +path = "lib.rs" + +[dependencies] +gui = { path = "../gui", version = "1.0" } diff --git a/creational/factory-method/render-dialog-alternative/windows_gui/button.rs b/creational/factory-method/render-dialog-alternative/windows_gui/button.rs new file mode 100644 index 0000000..f72af80 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/windows_gui/button.rs @@ -0,0 +1,14 @@ +use gui::button::Button; + +pub struct WindowsButton; + +impl Button for WindowsButton { + fn render(&self) { + println!("Drawing a Windows button"); + self.on_click(); + } + + fn on_click(&self) { + println!("Button: I'm a Windows button!"); + } +} diff --git a/creational/factory-method/render-dialog-alternative/windows_gui/dialog.rs b/creational/factory-method/render-dialog-alternative/windows_gui/dialog.rs new file mode 100644 index 0000000..8c933a9 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/windows_gui/dialog.rs @@ -0,0 +1,12 @@ +use gui::button::Button; +use gui::dialog::Dialog; + +use crate::button::WindowsButton; + +pub struct WindowsDialog; + +impl Dialog for WindowsDialog { + fn create_button(&self) -> Box { + Box::new(WindowsButton) + } +} diff --git a/creational/factory-method/render-dialog-alternative/windows_gui/lib.rs b/creational/factory-method/render-dialog-alternative/windows_gui/lib.rs new file mode 100644 index 0000000..4f4e867 --- /dev/null +++ b/creational/factory-method/render-dialog-alternative/windows_gui/lib.rs @@ -0,0 +1,2 @@ +pub mod button; +pub mod dialog; \ No newline at end of file