겨울 세일!
추상 팩토리

러스트로 작성된 추상 팩토리

추상 팩토리는 생성 디자인 패턴이며, 관련 객체들의 구상 클래스들을 지정하지 않고도 해당 객체들의 제품 패밀리들을 생성할 수 있도록 합니다.

추상 팩토리는 모든 고유한 제품들을 생성하기 위한 인터페이스를 정의하지만 실제 제품 생성은 구상 팩토리 클래스들에 맡깁니다. 또 각 팩토리 유형은 특정 제품군에 해당합니다.

클라이언트 코드는 생성자 호출​(new 연산자)​로 직접 제품들을 생성하는 대신 팩토리 객체의 생성 메서드들을 호출합니다. 팩토리는 단일 제품 변형에 해당하므로 해당 팩토리의 모든 제품이 호환될 것입니다.

클라이언트 코드는 추상 인터페이스를 통해서만 팩토리 및 제품과 함께 작동하며, 이렇게 하면 클라이언트 코드가 팩토리 객체에 의해 생성된 모든 제품 변형과 함께 작동할 수 있습니다. 새로운 구상 팩토리 클래스를 생성한 후 클라이언트 코드에 전달합니다.

다양한 팩토리 패턴들과 개념들의 차이점을 이해하지 못하셨다면 팩토리 비교를 읽어보세요.

GUI Elements Factory

This example illustrates how 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 two approaches to implementing abstract factories in Rust:

  • using generics (static dispatch)
  • using dynamic allocation (dynamic dispatch)

When you're given a choice between static and dynamic dispatch, there is rarely a clear-cut correct answer. You'll want to use static dispatch in your libraries and dynamic dispatch in your binaries. In a library, you want to allow your users to decide what kind of dispatch is best for them since you don't know what their needs are. If you use dynamic dispatch, they're forced to do the same, whereas if you use static dispatch, they can choose whether to use dynamic dispatch or not.

gui: Abstract Factory and Abstract Products

gui/lib.rs

pub trait Button {
    fn press(&self);
}

pub trait Checkbox {
    fn switch(&self);
}

/// Abstract Factory defined using generics.
pub trait GuiFactory {
    type B: Button;
    type C: Checkbox;

    fn create_button(&self) -> Self::B;
    fn create_checkbox(&self) -> Self::C;
}

/// Abstract Factory defined using Box pointer.
pub trait GuiFactoryDynamic {
    fn create_button(&self) -> Box<dyn Button>;
    fn create_checkbox(&self) -> Box<dyn Checkbox>;
}

macos-gui: One family of products

macos-gui/lib.rs

pub mod button;
pub mod checkbox;
pub mod factory;

windows-gui: Another family of products

windows-gui/lib.rs

pub mod button;
pub mod checkbox;
pub mod factory;

Static dispatch

Here, the abstract factory is implemented via generics which lets the compiler create a code that does NOT require dynamic dispatch in runtime.

app: Client code with static dispatch

app/main.rs

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);
    }
}

app/render.rs

//! 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();
}

Dynamic dispatch

If a concrete type of abstract factory is not known at the compilation time, then is should be implemented using Box pointers.

app-dyn: Client code with dynamic dispatch

app-dyn/main.rs

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);
}

app-dyn/render.rs

//! 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();
}

Output

Windows button has pressed
Windows button has pressed
Windows checkbox has switched
Windows checkbox has switched

다른 언어로 작성된 추상 팩토리

C#으로 작성된 추상 팩토리 C++로 작성된 추상 팩토리 Go로 작성된 추상 팩토리 자바로 작성된 추상 팩토리 PHP로 작성된 추상 팩토리 파이썬으로 작성된 추상 팩토리 루비로 작성된 추상 팩토리 스위프트로 작성된 추상 팩토리 타입스크립트로 작성된 추상 팩토리