Factory Method

The factory method pattern provides an interface for creating objects:

trait Animal {
    fn make_sound(&self) -> &str;
}

struct Dog;
impl Animal for Dog {
    fn make_sound(&self) -> &str {
        "Woof!"
    }
}

struct Cat;
impl Animal for Cat {
    fn make_sound(&self) -> &str {
        "Meow!"
    }
}

enum AnimalType {
    Dog,
    Cat,
}

// Factory function
fn create_animal(animal_type: AnimalType) -> Box<dyn Animal> {
    match animal_type {
        AnimalType::Dog => Box::new(Dog),
        AnimalType::Cat => Box::new(Cat),
    }
}

fn main() {
    let dog = create_animal(AnimalType::Dog);
    let cat = create_animal(AnimalType::Cat);
    
    println!("Dog says: {}", dog.make_sound());
    println!("Cat says: {}", cat.make_sound());
}

Singleton Pattern

While traditional singletons are less common in Rust, we can achieve similar functionality:

use std::sync::{Arc, Mutex, Once};
use std::sync::atomic::{AtomicUsize, Ordering};

struct Logger {
    log_count: AtomicUsize,
}

impl Logger {
    fn log(&self, message: &str) {
        let count = self.log_count.fetch_add(1, Ordering::SeqCst);
        println!("[{}] {}", count, message);
    }
}

// Thread-safe singleton
fn get_logger() -> Arc<Logger> {
    static INIT: Once = Once::new();
    static mut LOGGER: Option<Arc<Logger>> = None;
    
    unsafe {
        INIT.call_once(|| {
            LOGGER = Some(Arc::new(Logger {
                log_count: AtomicUsize::new(0),
            }));
        });
        
        LOGGER.clone().unwrap()
    }
}

fn main() {
    let logger1 = get_logger();
    let logger2 = get_logger();
    
    logger1.log("Hello from logger1");
    logger2.log("Hello from logger2");
    logger1.log("Hello again from logger1");
}

Structural Patterns

Patterns that focus on object composition:

Adapter Pattern

The adapter pattern allows objects with incompatible interfaces to work together:

// Existing interface
trait OldSystem {
    fn legacy_operation(&self, data: &str) -> String;
}

// New interface
trait NewSystem {
    fn modern_operation(&self, input: &str) -> String;
}

// Concrete implementation of the old system
struct LegacySystem;

impl OldSystem for LegacySystem {
    fn legacy_operation(&self, data: &str) -> String {
        format!("Legacy: {}", data.to_uppercase())
    }
}

// Adapter that makes the old system compatible with the new interface
struct SystemAdapter {
    legacy: LegacySystem,
}

impl NewSystem for SystemAdapter {
    fn modern_operation(&self, input: &str) -> String {
        // Adapt the call to the legacy system
        self.legacy.legacy_operation(input)
    }
}

// Client code that works with the new interface
fn process_with_new_system(system: &impl NewSystem, input: &str) {
    let result = system.modern_operation(input);
    println!("Result: {}", result);
}

fn main() {
    // Using the adapter
    let legacy = LegacySystem;
    let adapter = SystemAdapter { legacy };
    
    process_with_new_system(&adapter, "hello world");
}

Decorator Pattern

The decorator pattern adds behavior to objects dynamically:

// Base component trait
trait DataSource {
    fn read(&self) -> String;
    fn write(&mut self, data: &str);
}

// Concrete component
struct FileDataSource {
    filename: String,
    data: String,
}

impl FileDataSource {
    fn new(filename: &str) -> Self {
        FileDataSource {
            filename: filename.to_string(),
            data: String::new(),
        }
    }
}

impl DataSource for FileDataSource {
    fn read(&self) -> String {
        println!("Reading from file: {}", self.filename);
        self.data.clone()
    }
    
    fn write(&mut self, data: &str) {
        println!("Writing to file: {}", self.filename);
        self.data = data.to_string();
    }
}

// Base decorator
struct DataSourceDecorator<T: DataSource> {
    wrapped: T,
}

impl<T: DataSource> DataSource for DataSourceDecorator<T> {
    fn read(&self) -> String {
        self.wrapped.read()
    }
    
    fn write(&mut self, data: &str) {
        self.wrapped.write(data)
    }
}

// Concrete decorator: encryption
struct EncryptionDecorator<T: DataSource> {
    source: DataSourceDecorator<T>,
}

impl<T: DataSource> EncryptionDecorator<T> {
    fn new(source: T) -> Self {
        EncryptionDecorator {
            source: DataSourceDecorator { wrapped: source },
        }
    }
    
    // Simple "encryption" for demonstration
    fn encrypt(&self, data: &str) -> String {
        data.chars().map(|c| ((c as u8) + 1) as char).collect()
    }
    
    fn decrypt(&self, data: &str) -> String {
        data.chars().map(|c| ((c as u8) - 1) as char).collect()
    }
}

impl<T: DataSource> DataSource for EncryptionDecorator<T> {
    fn read(&self) -> String {
        let data = self.source.read();
        println!("Decrypting data");
        self.decrypt(&data)
    }
    
    fn write(&mut self, data: &str) {
        println!("Encrypting data");
        let encrypted = self.encrypt(data);
        self.source.write(&encrypted);
    }
}

Composite Pattern

The composite pattern treats individual objects and compositions of objects uniformly:

use std::collections::HashMap;

// Component trait
trait FileSystemNode {
    fn name(&self) -> &str;
    fn size(&self) -> usize;
    fn print(&self, indent: usize);
}

// Leaf node
struct File {
    name: String,
    content: Vec<u8>,
}

impl File {
    fn new(name: &str, content: Vec<u8>) -> Self {
        File {
            name: name.to_string(),
            content,
        }
    }
}

impl FileSystemNode for File {
    fn name(&self) -> &str {
        &self.name
    }
    
    fn size(&self) -> usize {
        self.content.len()
    }
    
    fn print(&self, indent: usize) {
        println!("{:indent$}File: {} ({} bytes)", "", self.name, self.size(), indent = indent);
    }
}

// Composite node
struct Directory {
    name: String,
    children: HashMap<String, Box<dyn FileSystemNode>>,
}

impl Directory {
    fn new(name: &str) -> Self {
        Directory {
            name: name.to_string(),
            children: HashMap::new(),
        }
    }
    
    fn add(&mut self, node: impl FileSystemNode + 'static) {
        self.children.insert(node.name().to_string(), Box::new(node));
    }
}

impl FileSystemNode for Directory {
    fn name(&self) -> &str {
        &self.name
    }
    
    fn size(&self) -> usize {
        self.children.values().map(|child| child.size()).sum()
    }
    
    fn print(&self, indent: usize) {
        println!("{:indent$}Directory: {} ({} bytes)", "", self.name, self.size(), indent = indent);
        for child in self.children.values() {
            child.print(indent + 4);
        }
    }
}