디자인 패턴 - 팩토리(Factory) 기본 개념

팩토리(Factory)


팩토리 패턴은 객체 생성을 위한 디자인 패턴 중 하나입니다. 이 패턴의 주요 목적은 객체 생성 로직을 클라이언트 코드로부터 분리하는 것입니다.

factory

그럼 이제 피자를 만드는 가게를 운영한다고 생각하고 팩토리 패턴 대해 알아봅시다!

step 1. 치즈 피자만 파는 가게를 열자


상황

치즈 피자만 만드는 가게를 오픈했습니다. 들어오는 주문은 한 종류, ‘치즈 피자’ 만 만들면 됩니다.

치즈 피자 가게

코드

주문에 들어오는 피자 종류는 한가지 뿐이니, 고민할 필요도 없이 치즈 피자만 만들면 됩니다.

from enum import StrEnum

class PizzaType(StrEnum):
    CHEESE = 'cheese pizza'
class PizzStore:
    def order(self, pizza_type: PizzaType) -> Pizza:
        pizza = CheesePizza()
        return pizza

클래스 다이어그램

PizzaStore 내부에 CheesePizza 객체가 포함되어 있는 구성(composition) 형태의 구조를 가지게 됩니다. 구성(composition) 형태는 기본적으로 객체간 강한 결합 관계를 형성하게 됩니다. 그래도 음… 이 정도는 딱히 문제는 없어 보입니다.

%%{
  init: {
    'theme': 'default',
    'themeVariables': {
      'lineColor': '#F8B229',
      'tertiaryColor': '#fff'
    }
  }
}%%
classDiagram
    direction BT

    class PizzaStore {
        +order() Pizza
    }

    class CheesePizza

    PizzaStore *-- CheesePizza

step 2. 피자 종류를 늘리자!


상황

장사가 잘되서 기쁜 마음으로 피자 종류를 다양하게 늘렸습니다.

“왜 이렇게 자꾸 장사가 잘되는데?!”

장사가 잘돼

들어오는 주문에 따라 수많은 종류의 피자를 만들어야 합니다. 피자 종류가 많아질수록 점점 더 힘들어 집니다. 메뉴가 많다보니 많이 안 팔리는 메뉴는 또 빼야만 할것 같습니다.

코드

이제 주문에 들어오는 피자 종류에 따라서 각각의 피자를 만들어야 합니다. 그래서 분기문을 추가 합니다. 하지만 피자 종류를 추가할때 마다 점점 더 많은 분기문이 필요해집니다.

from enum import StrEnum

class PizzaType(StrEnum):
    CHEESE = 'cheese pizza'
    CLAM = 'clam pizza' # 추가
    HAM = 'ham pizza'   # 추가
class PizzStore:
    def order(self, pizza_type: PizzaType) -> Pizza:
        match pizza_type:
            case pizza_type.CHEESE:
                pizza = CheesePizza()
            case pizza_type.CLAM:
                pizza = ClamPizza()
            case pizza_type.HAM:
                pizza = HamPizza()
            #
            # 점점 많아지는 분기문...
            #

        return pizza

클래스 다이어그램

복잡한 분기문 만큼 심각한 문제가 또 있습니다. 바로 PizzaStore 라는 클라이언트 객체 입장에서, 점점 많아지는 여러가지 Pizza 클래스에 대해서 강하게 결합한다는 문제가 있습니다.

%%{
  init: {
    'theme': 'default',
    'themeVariables': {
      'lineColor': '#F8B229',
      'tertiaryColor': '#fff'
    }
  }
}%%
classDiagram
    direction BT

    class PizzaStore {
        +order() Pizza
    }

    class CheesePizza
    class ClamPizza
    class HamPizza

    PizzaStore *-- CheesePizza
    PizzaStore *-- ClamPizza
    PizzaStore *-- HamPizza

step 3. 공장에서 만들어진 피자를 받자


상황

이제 직접 수많은 종류의 피자를 직접 만들지 않기로 합니다. 공장에서 만들어진 피자를 받아서 팔기로 했습니다. 공장에서 피자를 받아오니 다양한 피자를 만들기 위해 복잡했던 가게가 깔금하게 정리됐습니다!

피자 공장

코드

여러가지 피자를 만들려다 보니 피자 가게 클래스가 복잡했습니다. 이걸 피자만 만드는 팩토리 객체를 통해서 피자를 받아오게 변경합니다. 클라이언트 객체에서 여러가지 피자에 대한 의존이 없어집니다.

from enum import StrEnum

class PizzaType(StrEnum):
    CHEESE = 'cheese pizza'
    CLAM = 'clam pizza'
    HAM = 'ham pizza'
class PizzStore:
    def __init__(self):
        self.factory = PizzaFactory()

    def order(self, pizza_type: PizzaType) -> Pizza:
        pizza = self.factory.make_pizza(pizza_type) # 깔금해진 가게
        return pizza
class PizzFactory:
    def make_pizza(self, pizza_type: PizzaType):
        match pizza_type:
            case pizza_type.CHEESE:
                pizza = CheesePizza()
            case pizza_type.CLAM:
                pizza = ClamPizza()
            case pizza_type.HAM:
                pizza = HamPizza()

        return pizza

장점이 뭐야?


팩토리 클래스를 작성해 보면 이런 생각이 듭니다.

복잡한 문제를 그냥 다른 객체로 넘긴거 아니야?

물론 그렇게 보일 수 있습니다. 지금까지는 팩토리 객체를 사용하는 객체가 하나라고만 생각했기 때문일 수 있습니다. 좀더 확장해서 생각해 봅시다. 팩토리 객체를 사용하는 다른 객체가 많아지면 어떨까요?

코드

팩토리를 사용함으로써 PizzStorePizzFoodTruck 어디에서도 피자에 대한 흔적을 찾아 볼 수 없습니다.

class PizzFactory:
    def make_pizza(self, pizza_type: PizzaType):
        match pizza_type:
            case pizza_type.CHEESE:
                pizza = CheesePizza()
            case pizza_type.CLAM:
                pizza = ClamPizza()
            case pizza_type.HAM:
                pizza = HamPizza()

            # 푸드트럭에서만 파는 메뉴
            case pizza_type.MINI_CHEESE:
                pizza = MiniHamPizza()
            case pizza_type.MINI_CLAM:
                pizza = MiniHamPizza()
            case pizza_type.MINI_HAM:
                pizza = MiniHamPizza()

        return pizza
class PizzStore:
    def __init__(self):
        self.factory = PizzaFactory()

    def order(self, pizza_type: PizzaType):
        pizza = self.factory.make_pizza(pizza_type) # 가게에서도
        return Pizza
class PizzFoodTruck:
    def __init__(self):
        self.factory = PizzaFactory()

    def order(self, pizza_type: PizzaType):
        pizza = self.factory.make_pizza(pizza_type) # 푸드트럭에서도 
        return Pizza

클래스 다이어그램 : SimpleFactory

이처럼 팩토리 패턴을 통해서 피자 자체에 대한 의존성을 제거 할 수 있습니다. 복잡하게 생성해야 하는 각 피자 객체를 하나의 팩토리 객체를 통해 해결 할 수 있게 됐습니다!

%%{
  init: {
    'theme': 'default',
    'themeVariables': {
      'lineColor': '#F8B229',
      'tertiaryColor': '#fff'
    }
  }
}%%
classDiagram
    direction BT

    class PizzaStore {
        -factory : PizzaFactory

        +order() Pizza
    }

    class PizzaFoodTruck {
        -factory : PizzaFactory

        +order() Pizza
    }

    class PizzaFactory{
        +make_pizza() Pizza
    }

    class CheesePizza
    class MiniCheesePizza

    PizzaStore ..> PizzaFactory
    PizzaFoodTruck ..> PizzaFactory

    PizzaFactory *-- CheesePizza
    PizzaFactory *-- MiniCheesePizza

팩토리 패턴의 종류


팩토리 패턴에도 여러가지 종류가 있습니다. 앞서 본 패턴은 가장 기본적인 구조로 SimpleFactory 패턴이라고 합니다. 크게 세 가지 패턴을 많이 이야기 합니다.

  1. SimpleFactory
  2. MethodFactory
  3. AbstractFactory

결론


팩토리 패턴은 다른 객체를 생성하는 로직을 분리하는데 있습니다. 객체를 생성하는 로직을 분리함으로써 장점은 크게 두 가지 입니다.

  1. 클라이언트 객체에서 다른 객체를 생성하는 복잡한 의존을 제거
  2. 다른 클라이언트 객체도 해당 팩토리를 재사용 가능
  3. 관리 포인트를 줄일 수 있다

그럼 다음편에서 나머지 팩토리 패턴에 대해 알아보겠습니다! 😊

python 디자인패턴 팩토리