디자인 패턴 - 팩토리(Factory) 심화

메서드 팩토리(Method Factory), 추상 팩토리(Abstract Factory)에 대해서 알아보겠습니다.

written by tiaz0128

팩토리 패턴의 종류


앞서 본 패턴은 가장 기본적인 구조로 SimpleFactory 패턴이라고 합니다. 이번에는 나머지 두 가지 패턴에 대해서 알아보도록 하겠습니다.

  1. SimpleFactory
  2. MethodFactory
  3. AbstractFactory

메서드 팩토리 패턴(Method Factory)


객체 생성 로직을 서브클래스로 캡슐화하여 유연성과 확장성을 제공합니다. 객체 생성을 위한 인터페이스를 정의하지만, 어떤 클래스의 인스턴스를 생성할지는 서브클래스가 결정하도록 합니다.

상황

피자 가게가 너무너무 잘됐습니다. 각 도시마다 지점을 낸다고 생각해 보겠습니다. 그리고 각 지점은 같은 메뉴라도, 해당 지역의 특색이 감미되게 공장에서 재료를 가공해서 배달합니다.

도시마다 피자

코드

우선은 각 지역의 피자를 만들어 두겠습니다. 그리고 피자 분기문에 사용할 enum 타입을 하나 만들어 두겠습니다.

from abc import ABC

class Pizza(ABC):
    name = "피자"

class NYStyleCheesePizza(Pizza):
    name = "뉴욕 치즈 핏자"

class ChicagoStyleCheesePizza(Pizza):
    name = "시카고 치즈 핏자"
from enum import StrEnum

class PizzaType(StrEnum):
    CHEESE = "cheese"

이제 가장 중요한 팩토리 클래스를 작성하겠습니다. 이 패턴의 가장 핵심은 인터페이스에서 객체 생성 메서드를 선언하고, 하위 클래스가 이 메서드를 구현하는 것입니다.

즉 여러개의 팩토리 클래스가 만들어 질 수 있고, 각 팩토리 클래스마다 원하는 구현을 추가 할 수 있다는 장점이 있습니다.

from abc import ABC, abstractmethod
from typing import override


class MethodFactory(ABC):
    @abstractmethod
    def make_pizza(self, pizza_type):
        pass

class NYPizzaFactory(MethodFactory):
    @override
    def make_pizza(self, pizza_type):
        match pizza_type:
            case PizzaType.CHEESE:
                return NYStyleCheesePizza()

class ChicagoPizzaFactory(MethodFactory):
    @override
    def make_pizza(self, pizza_type):
        self.cut_rectangle()

        match pizza_type:
            case PizzaType.CHEESE:
                return ChicagoStyleCheesePizza()

    def cut_rectangle(self):
        print("시카고 피자는 모양이 네모납니다.")

이제 팩토리 객체를 사용하는 클라이언트는 객체 주입을 통해 각 상황에 알맞은 팩토리 객체를 받아서 사용하면 됩니다. 뉴욕 지점을 만들고 싶다면 뉴욕 팩토리를 받아서 피자를 만들고, 시카고 지점을 만들고 싶으면 시카고 팩토리를 넣어주면 되는거죠.

class PizzaStore:
    def __init__(self, factory: MethodFactory) -> None:
        self.factory = factory

    def order(self, pizza_type: PizzaType) -> Pizza:
        pizza = self.factory.make_pizza(pizza_type)
        return pizza
if __name__ == "__main__":
    ny_factor = NYPizzaFactory()
    ny_store = PizzaStore(ny_factor)
    ny_store.order(PizzaType.CHEESE)

    ch_factory = ChicagoPizzaFactory()
    ch_store = PizzaStore(ch_factory)
    ch_store.order(PizzaType.CHEESE)

같은 치즈 피자라도 각 팩토리를 통해서 다양한 특색을 갖게 만들 수 있습니다.

뉴욕 치즈 핏자를 주문하셨습니다.

시카고 피자는 모양이 네모납니다.
시카고 치즈 핏자를 주문하셨습니다.

클래스 다이어그램 : MethodFactory

%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a3844',
      'lineColor': '#fff',
      'primaryTextColor': '#fff',
      'tertiaryColor': '#fff'
    }
  }
}%%

classDiagram
    class PizzaStore{
        -MethodFactory factory
        +order() Pizza
    }

    class MethodFactory {
        <<interface>>
        +makePizza(type) Pizza*
    }

    class NYPizzaFactory {
        +makePizza(type) Pizza
    }

    class ChicagoPizzaFactory {
        +makePizza(type) Pizza
        +cut_rectangle()
    }

    PizzaStore o-- MethodFactory
    MethodFactory <|.. NYPizzaFactory
    MethodFactory <|.. ChicagoPizzaFactory

    class Pizza {
        <<abstract>>
        str name
    }

    class NYStyleCheesePizza {
        str name
    }

    class ChicagoStyleCheesePizza {
        str name
    }

    NYStyleCheesePizza --|> Pizza
    ChicagoStyleCheesePizza --|> Pizza

    NYPizzaFactory *-- NYStyleCheesePizza
    ChicagoPizzaFactory *-- ChicagoStyleCheesePizza

추상 팩토리(Abstract Factory)


추상 팩토리 패턴은 관련된 객체들의 군집을 생성하기 위한 인터페이스를 제공하는 생성 패턴입니다. 서로 관련되거나 의존적인 객체들의 모아두고 생성하는 특징이 있습니다.

상황

피자에 필요한 각 재료를 만드는 공장을 한번 생각해 봅시다. 공장은 피자에 필요한 도우와 치즈를 만듭니다. 각 공장은 서로 다른 도우와 치즈를 만들고 있습니다.

각 지점에서는 공장에서 받은 재료들로 피자를 만들면 됩니다.

피자 공장

코드

우선은 각각의 재료를 만들겠습니다. 도우와 치즈를 추상적인 형태로 만들고 이를 구체적인 재료 클래스에서 구현하면 됩니다.

from abc import ABC, abstractmethod
from typing import override

class Dough(ABC):
    @abstractmethod
    def __str__(self) -> str:
        pass

class ThinCrustDough(Dough):
    @override
    def __str__(self) -> str:
        return "ThinCrustDough"

class ThickCrustDough(Dough):
    @override
    def __str__(self) -> str:
        return "ThickCrustDough"
class Cheese(ABC):
    @abstractmethod
    def __str__(self) -> str:
        pass

class ReggianoCheese(Cheese):
    def __str__(self) -> str:
        return "ReggianoCheese"

class MozzarellaCheese(Cheese):
    def __str__(self) -> str:
        return "MozzarellaCheese"

팩토리 인터페이스를 만들어야 하는데, 각 재료별 객체를 생성하는 메서드 선언합니다. 카테고리별로 추상 메서드를 선언해둔다고 생각하면 됩니다. 실제 팩토리에서 서로 관련되거나 의존적인 객체들을 카테고리별로 구현해주면 됩니다.

여기서는 공장에 재료별로 하나씩 선택해서 묶어보도록 하겠습니다.

from abc import ABC, abstractmethod

class AbstractFactory(ABC):
    name = "공장"

    @abstractmethod
    def make_dough(self) -> Dough:
        pass

    @abstractmethod
    def make_cheese(self) -> Cheese:
        pass
class NYPizzaFactory(AbstractFactory):
    name = "뉴욕 공장"

    def make_dough(self) -> Dough:
        return ThinCrustDough()

    def make_cheese(self) -> Cheese:
        return ReggianoCheese()


class ChicagoPizzaFactory(AbstractFactory):
    name = "시카고 공장"

    def make_dough(self) -> Dough:
        return ThickCrustDough()

    def make_cheese(self) -> Cheese:
        return MozzarellaCheese()


이제 클라이언트에서 각 팩토리를 통해 연관이 있는 재료들을 받을 수 있습니다. 뉴욕 공장에서 받은 치즈와 도우로 뉴욕 스타일의 피자를! 시카고 공장에서 받은 치즈와 도우로 시카고 스타일의 피자를 만들수 있게 됩니다!

class PizzaStore:
    def __init__(self, factory: AbstractFactory):
        self.factory = factory

    def order(self):
        cheese = self.factory.make_cheese()
        dough = self.factory.make_dough()

        return self.make_pizza(cheese, dough)

    def make_pizza(self, cheese, dough):
        print(f"{self.factory.name}에서 받은")
        print(f"{cheese} 치즈와 {dough} 도우를 사용하여 피자를 만듭니다.")

if __name__ = "__main__":
    ny_factor = NYPizzaFactory()
    ny_store = PizzaStore(ny_factor)
    ny_store.order()

    ch_factory = ChicagoPizzaFactory()
    ch_store = PizzaStore(ch_factory)
    ch_store.order()
뉴욕 공장에서 받은
ReggianoCheese 치즈와 ThinCrustDough 도우를 사용하여 피자를 만듭니다.


시카고 공장에서 받은
MozzarellaCheese 치즈와 ThickCrustDough 도우를 사용하여 피자를 만듭니다.

클래스 다이어그램 : Abstract Factory

%%{
  init: {
    'theme': 'base',
    'themeVariables': {
      'primaryColor': '#2a3844',
      'lineColor': '#fff',
      'primaryTextColor': '#fff',
      'tertiaryColor': '#fff'
    }
  }
}%%

classDiagram
    direction RL

    class Dough {
        <<abstract>>
    }

    class Cheese {
        <<abstract>>
    }

    class ThinCrustDough {
        
    }

    class ThickCrustDough {
        
    }

    ThinCrustDough --|> Dough
    ThickCrustDough --|> Dough

    class AbstractFactory {
        <<interface>>
        +makeDough() Dough*
        +makeCheese() Cheese*
    }

    class NYPizzaFactory {
        +makeDough() Dough
        +makeCheese() Cheese
    }
    
    class ChicagoPizzaFactory {
        +makeDough() Dough
        +makeCheese() Cheese
    }
    
    class ReggianoCheese {
    }

    class MozzarellaCheese {

    }

    class PizzaStore{
        -AbstractFactory factory
        +order() Pizza
        -make_pizza(cheese, dough) Pizza
    }

    ReggianoCheese --|> Cheese
    MozzarellaCheese --|> Cheese

    NYPizzaFactory *-- ReggianoCheese
    NYPizzaFactory *-- ThinCrustDough

    ChicagoPizzaFactory *-- MozzarellaCheese
    ChicagoPizzaFactory *-- ThickCrustDough

    AbstractFactory <|.. NYPizzaFactory
    AbstractFactory <|.. ChicagoPizzaFactory

    AbstractFactory --o PizzaStore

정리


팩토리 메서드 패턴

추상 팩토리 패턴

추상 팩토리는 실제로 여러 개의 “팩토리 메서드”를 포함하고 있는 형태라고 볼 수 있습니다.

python 디자인패턴 팩토리

tiaz0128

Eat Sleep Coding.

Never Never Giveup.

💫 Python  |  BackEnd  |  Cloud Architect  |  DevOps