Creational Patterns

Implement a Factory

//! A factory is quite useful when you need to create objects of different types
//! that share a common interface, without needing to know the exact type at
//! compile time.

// Define a common trait for all shapes.
// This defines the common interface that all concrete shape types will
// implement.
trait Shape {
    fn draw(&self);
}

// A concrete implementation of the Shape trait.
struct Circle {
    radius: f64,
}

impl Shape for Circle {
    fn draw(&self) {
        println!("Drawing a circle with radius: {}", self.radius);
    }
}

// Another concrete implementation.
struct Square {
    side: f64,
}

impl Shape for Square {
    fn draw(&self) {
        println!("Drawing a square with side: {}", self.side);
    }
}

// A enum that represents the types of shapes we can create.
enum ShapeType {
    Circle,
    Square,
}

// The Factory.
struct ShapeFactory;

impl ShapeFactory {
    // The logic for creating different shape objects is centralized.
    fn create_shape(shape_type: ShapeType, size: f64) -> Box<dyn Shape> {
        match shape_type {
            ShapeType::Circle => Box::new(Circle { radius: size }),
            ShapeType::Square => Box::new(Square { side: size }),
        }
    }
}

fn main() {
    // Using the factory to create different shapes.
    let circle = ShapeFactory::create_shape(ShapeType::Circle, 5.0);
    let square = ShapeFactory::create_shape(ShapeType::Square, 10.0);

    // We use the ShapeFactory to create instances of Circle and Square without
    // directly knowing their concrete types. We only interact with them
    // through the Shape trait.
    circle.draw();
    square.draw();
}

Implement an Abstract Factory

//! The Abstract Factory pattern creates families of related objects without
//! tying-in to their concrete implementations.
//!
//! - The client code (in `main()`) doesn't need to know the specific classes of
//!   the objects it creates.
//! - The pattern ensures that the objects created belong to the same family,
//!   enforcing consistency.
//!
//! The following example simulates building a UI library that needs to support
//! different themes, like "Light" and "Dark." Each theme will have its own set
//! of widgets: buttons, text boxes, and so on. The Abstract Factory pattern
//! lets you define an interface for creating these families of widgets,
//!  and then you can have concrete factories for each theme.

// Operations common to all concrete objects.
trait Button {
    fn render(&self);
}

trait TextBox {
    fn display_text(&self, text: &str);
}

// Abstract factory interface (trait).
trait GUIFactory {
    fn create_button(&self) -> Box<dyn Button>;
    fn create_text_box(&self) -> Box<dyn TextBox>;
}

mod light {
    use super::*;

    // Concrete object implementations for Light Theme.
    struct LightButton;

    impl Button for LightButton {
        fn render(&self) {
            println!("Rendering a light button.");
        }
    }

    struct LightTextBox;

    impl TextBox for LightTextBox {
        fn display_text(&self, text: &str) {
            println!("Displaying '{}' in a light text box.", text);
        }
    }

    // The concrete factory implementation produce concrete objects belonging to
    // a specific family (here, light theme).
    pub struct LightGUIFactory;

    impl GUIFactory for LightGUIFactory {
        fn create_button(&self) -> Box<dyn Button> {
            Box::new(LightButton)
        }

        fn create_text_box(&self) -> Box<dyn TextBox> {
            Box::new(LightTextBox)
        }
    }
}

mod dark {

    use super::*;

    // Concrete object implementations for Dark Theme.
    struct DarkButton;

    impl Button for DarkButton {
        fn render(&self) {
            println!("Rendering a dark button.");
        }
    }

    struct DarkTextBox;

    impl TextBox for DarkTextBox {
        fn display_text(&self, text: &str) {
            println!("Displaying '{}' in a dark text box.", text);
        }
    }

    // Concrete Factory for Dark Theme.
    pub struct DarkGUIFactory;

    impl GUIFactory for DarkGUIFactory {
        fn create_button(&self) -> Box<dyn Button> {
            Box::new(DarkButton)
        }

        fn create_text_box(&self) -> Box<dyn TextBox> {
            Box::new(DarkTextBox)
        }
    }
}

fn render(factory: &dyn GUIFactory, txt: &str) {
    let button = factory.create_button();
    let text_box = factory.create_text_box();
    button.render();
    text_box.display_text(txt);
}

fn main() {
    // Using the Light theme factory.
    render(&light::LightGUIFactory, "Hello from light theme!");
    println!();

    // Using the Dark theme factory.
    render(&dark::DarkGUIFactory, "Hello from dark theme!");
}

See also:

Implement a Singleton


use std::sync::Mutex;
use std::sync::OnceLock;

struct Settings {
    config_value: String,
}

impl Settings {
    fn new(value: String) -> Self {
        Settings {
            config_value: value,
        }
    }

    fn get_config(&self) -> &str {
        &self.config_value
    }

    fn set_config(&mut self, new_value: String) {
        self.config_value = new_value;
    }
}

// OnceCell is a thread-safe cell that can be written to only once.
// We wrap our Settings instance in a Mutex to ensure thread-safe access and
// modification. Since multiple threads might try to access the singleton, we
// need to protect it from race conditions.
static SETTINGS: OnceLock<Mutex<Settings>> = OnceLock::new();
// You could also use `std::sync::LazyLock`.

// Single point of access to our Settings instance.
fn get_settings() -> &'static Mutex<Settings> {
    // If SETTINGS hasn't been initialized yet, execute the closure.
    SETTINGS
        .get_or_init(|| Mutex::new(Settings::new("default_config".to_string())))
}

fn main() {
    // `OnceLock` has not been written to yet.
    assert!(SETTINGS.get().is_none());

    // Acquire a lock.
    let settings_guard_1 = get_settings().lock().unwrap();
    println!("Config 1: {}", settings_guard_1.get_config());
    drop(settings_guard_1); // Release the lock.

    {
        let mut settings_guard_2 = get_settings().lock().unwrap();
        // The MutexGuard provides mutable access.
        settings_guard_2.set_config("new_config".to_string());
        println!("Config 2 updated!");
    } // Lock released here.

    let settings_guard_3 = get_settings().lock().unwrap();
    println!("Config 3: {}", settings_guard_3.get_config());
}