티스토리 뷰

🌻 Abstract Factory

 

📝 한 줄 정리

Factory에서 생성 하는 것 : product => product family 로 확장

 

🤔 해결할 문제 상황

가구샵 시뮬레이터를 만들고 있는 상황을 가정해보자. 다음과 같은 것을 표현할 클래스들이 필요하다.

1. 연관된 가구 품목의 패밀리 : 의자 + 쇼파 + 커피 테이블

2. 해당 패밀리의 여러 variant : 모던 스타일 / 빅토리안 스타일 / 아르데코 스타일

같은 스타일에 매칭되는 개별 가구 객체를 생성할 수 있어야 한다. 

그리고 프로그램에 새로운 상품이나 제품군을 추가하려고 할 때 기존 코드를 수정하고 싶지 않다. 

 

🧐 해결 방법

각 제품군에 대한 인터페이스를 명시적으로 선언한다음 각 (스타일의)제품이 해당 인터페이스를 구현하도록 만든다.

 

그 다음 Abstract Factory 선언하기. 제품군의 각 제품에 대한 생성 메소드 리스트(예를 들어 createChair, createCoffeeTable, createSofa) 를 가지고 있는 인터페이스. 위에서 정의한 abstract 제품 타입을 리턴하는 것이어야 한다.

그리고 AbstractFactory 인터페이스에 기반해서 각 제품군 variant에 대한 팩토리 클래스를 정의한다. (팩토리는 특정 종류의 product를 리턴하는 클래스다).

클라이언트에 전달한 팩토리 타입을 바꾸는 것으로, 클라이언트 코드 변경 없이 제품군 variant를 교체할 수 있음..

예를들어 클라이언트가 팩토리에서 chair를 얻고 싶을 때, 클라이언트는 모던 chair인지, 빅토리안 chair인지 (팩토리 클래스를 신경 쓰지 않고) 모든 의자를 추상 Chair 인터페이스를 통해 같은 방식으로 취급하고 싶다. 

이 접근 방식에서 클라이언트가 아는 점은 모든 의자가 sitOn 메소드를 구현하고 있다는 점 뿐이다. 또한 어떤 의자를 받았던 간 같은 팩토리를 사용한다면 같은 스타일의 소파, 커피테이블을 받을 수 있다.

 

명확히 할 점이 하나 남았다 : 실제 factory 객체는 어디서 만들까?

대부분 앱에서 concrete 팩토리 객체는 초기화 단계에 생성한다. 앱은 configuration이나 환경 변수 세팅에 따라 어떤 팩토리 타입을 사용할지 고른다.

 

🏗구조

구성 요소

Abstract Products / Concrete Products

AbstractFactory / Concrete Factories

 

👩‍💻 pseudo 코드

// abstract 팩토리 인터페이스는 각 abstract product를 리턴하는 메소드들을 선언한다.
// 이 products들은 family라고 불리며 서로 같은 테마나 컨셉으로 연관되어 있는 관계다.
// 한 family안에 있는 product끼리는 대부분 서로 콜라보 할 수 있음.
// 제품군은 여러 variant가 존재할 수 있다.
// 하지만 한 variant안의 제품은 다른 vairant의 제품과는 호환 불가능 함.
interface GUIFactory is
    method createButton():Button
    method createCheckbox():Checkbox


// Concrete 팩토리는 하나의 variant에 속하는 제품들을 생산
// 이 팩토리로 부터 생성되는 제품들은 서로 호환 가능 하다는 것을 보장한다. 
// 메소드 시그니처에 적힌 리턴 value는 abstract product임. 실제 리턴하는 것은 concrete product
class WinFactory implements GUIFactory is
    method createButton():Button is
        return new WinButton()
    method createCheckbox():Checkbox is
        return new WinCheckbox()

// 각 concrete 팩토리는 대응하는 제품 variant를 생산. 
class MacFactory implements GUIFactory is
    method createButton():Button is
        return new MacButton()
    method createCheckbox():Checkbox is
        return new MacCheckbox()

// 각 개별 제품은 base 인터페이스를 상속 받아야 함.
interface Button is
    method paint()

// Concrete products 는 대응되는 concrete 팩토리에서 생성됨
class WinButton implements Button is
    method paint() is
        // 윈도우 스타일 버튼 랜더링

class MacButton implements Button is
    method paint() is
        // 맥os 스타일 버튼 렌더링


interface Checkbox is
    method paint()

class WinCheckbox implements Checkbox is
    method paint() is
        // 윈도우 스타일 체크박스 렌더링

class MacCheckbox implements Checkbox is
    method paint() is
        // 맥os 스타일 체크박스 렌더링


// 클라이언트는 추상 타입에 대한 정보만 가지고 작성된다 : GUIFactory, Button and Checkbox.
// 어떤 타입의 factory, product subclass를 클라이언트에 전달했건 상관 없음.
class Application is
    private field factory: GUIFactory
    private field button: Button
    constructor Application(factory: GUIFactory) is
        this.factory = factory
    method createUI() is
        this.button = factory.createButton()
    method paint() is
        button.paint()

// 어플리케이션은 현재 config가 환경설정에 따라서 팩토리 타입을 고르고
// 런타임 (보통은 초기화 단계에서)에 생성한다.
class ApplicationConfigurator is
    method main() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            factory = new WinFactory()
        else if (config.OS == "Mac") then
            factory = new MacFactory()
        else
            throw new Exception("Error! Unknown operating system.")

        Application app = new Application(factory)

앱이 실행될때 현재 os 정보를 체크한다음, 해당 os에 맞는 팩토리 객체를 생성한다. 

이후 모든 코드에서 UI 요소 생성에 이 팩토리 객체 사용함으로써 잘못된 UI 요소가 생성되는 것을 방지한다.

클라이언트는 어떤 concrete factory, product 인지에 따라 분기처리하는 식의 코드 작성 없이 추상 인터페이스만 고려해서 코드 짜면 된다.

이렇게 하면 클라이언트 코드 수정 없이 나중에 새로운 UI 요소나 팩토리가 추가 하는 것도 가능. app 초기화 코드에 어떤 팩토리 사용할지 고르는 부분만 수정하면 됨.

 

 

🚀 구현 방법

1. product type, product family에 대한 표를 그려본다

2. 각 product type을 포괄하는 product 인터페이스를 정의하고 다른 concrete product 클래스가 이것 구현하도록 하기

3. 각 abstract product 생성 메소드를 담고 있는 factory 인터페이스 정의하기

4. product family마다 3번의 abstract factory 인터페이스 구현하는 concrete factory 클래스 만들기

5. app 초기화나 설정 부분에 factory initialization 코드 작성하기. concrete factory 클래스 들 중 하나를 인스턴스화 하는 코드.

이후의 제품 생성 코드는 여기서 만든 factory 객체를 가지고 실행될 것이다.

6. 기존 코드 중에 product 생성자로의 직접 호출이 있었다면 factory 객체의 creation 메소드를 호출하는 것으로 교체할 것.

 

 

🔮 장점

- 같은 factory에서 리턴하는 product는 모두 같은 family에 속함을 보장받을 수 있다

- concrete product와 클라이언트 코드가 강하게 coupling되는 것을 막을 수 있다.

- SRP(single responsibility principle) product 생성 코드를 한 곳에 몰아놓을 수 있어서 유지 보수 편리

- Open/Closed Principle 기존 클라이언트 코드 수정 없이 새로운 제품군 도입 가능

 

 

🧸 단점

- 패턴과 함께 여러 새로운 인터페이스, 클래스를 도입해야 하기 때문에 코드가 좀더 복잡해 보일 수 있다.

 

 

 

댓글
공지사항
최근에 올라온 글