Rust - Abstract Factory Pattern

Rust - Abstract Factory Pattern

In Rust, the Abstract Factory Pattern is a creational pattern that provides an interface for creating related objects without specifying their concrete classes. This pattern falls under the Gang of Four design patterns and is useful when a system should be independent of how its products are created, composed, and represented.

How it Works

The Abstract Factory Pattern is used to create families of related objects. It consists of two main components: the abstract factory and the concrete factory. The abstract factory defines the interface for creating a family of products, while the concrete factory implements this interface to create specific products.

The client code uses the abstract factory to create a family of related products without knowing their concrete classes. This way, the client code remains decoupled from the actual products it uses.

Example

As an example, let's consider the implementation of the Abstract Factory Pattern for creating a GUI toolkit in Rust. Here, we define a family of GUI elements, such as buttons, menus, and text boxes. The abstract factory provides an interface for creating these elements, while the concrete factory implements this interface to create specific GUI elements.

trait GUIFactory {
    fn create_button(&self) -> Box<dyn Button>;
    fn create_menu(&self) -> Box<dyn Menu>;
    fn create_textbox(&self) -> Box<dyn Textbox>;
}

trait Button {
    fn paint(&self);
}

trait Menu {
    fn display(&self);
}

trait Textbox {
    fn text(&self) -> String;
}

struct WinFactory {}

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

    fn create_menu(&self) -> Box<dyn Menu> {
        Box::new(WinMenu {})
    }

    fn create_textbox(&self) -> Box<dyn Textbox> {
        Box::new(WinTextbox {})
    }
}

struct LinuxFactory {}

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

    fn create_menu(&self) -> Box<dyn Menu> {
        Box::new(LinuxMenu {})
    }

    fn create_textbox(&self) -> Box<dyn Textbox> {
        Box::new(LinuxTextbox {})
    }
}

struct WinButton {}

impl Button for WinButton {
    fn paint(&self) {
        println!("Windows Button")
    }
}

struct LinuxButton {}

impl Button for LinuxButton {
    fn paint(&self) {
        println!("Linux Button")
    }
}

struct WinMenu {}

impl Menu for WinMenu {
    fn display(&self) {
        println!("Windows Menu")
    }
}

struct LinuxMenu {}

impl Menu for LinuxMenu {
    fn display(&self) {
        println!("Linux Menu")
    }
}

struct WinTextbox {}

impl Textbox for WinTextbox {
    fn text(&self) -> String {
        String::from("Windows Textbox")
    }
}

struct LinuxTextbox {}

impl Textbox for LinuxTextbox {
    fn text(&self) -> String {
        String::from("Linux Textbox")
    }
}

fn create_gui(factory: &dyn GUIFactory) {
    let button = factory.create_button();
    let menu = factory.create_menu();
    let textbox = factory.create_textbox();

    button.paint();
    menu.display();
    println!("{}", textbox.text());
}

fn main() {
    let win_factory = WinFactory {};
    let linux_factory = LinuxFactory {};

    create_gui(&win_factory);
    create_gui(&linux_factory);
}

In this example, we define the GUIFactory trait, which provides the interface for creating GUI elements. We then implement this trait for two concrete factories: WinFactory and LinuxFactory. Each concrete factory provides a specific implementation for creating GUI elements.

The create_gui function takes a reference to a GUIFactory object and uses it to create a family of related GUI elements. The main function creates instances of the WinFactory and LinuxFactory and passes them to the create_gui function to create different GUI elements for each platform.

Conclusion

The Abstract Factory Pattern provides a flexible way to create families of related objects without coupling the client code to their concrete classes. In Rust, this pattern can be used to create various types of systems, including GUI toolkits, database access layers, and more.


References

Did you find this article valuable?

Support Matthias Bruns by becoming a sponsor. Any amount is appreciated!