팩토리(Factory) 패턴 : 심화The Factory Pattern: Advanced

Method Factory와 Abstract Factory 패턴의 차이점과 구현 방법How Method Factory and Abstract Factory differ, and how to implement them

written by tiaz0128
'팩토리 기본 패턴'에 대해 더 자세히 알고 싶으시다면, "팩토리(Factory) 패턴 : 기초" 글을 확인 해주세요!

팩토리 패턴의 종류

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

  1. MethodFactory
  2. 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)

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

상황

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

  • 뉴욕 공장은 ThinCrustDough와 ReggianoCheese를 만듭니다.
  • 시카고 공장은 ThickCrustDough와 MozzarellaCheese를 만듭니다.

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

피자 공장

코드

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

  • Dough : ThinCrustDough / ThickCrustDough
  • Cheese : ReggianoCheese / MozzarellaCheese
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"

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

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

  • 뉴욕 공장 : ThinCrustDough + ReggianoCheese
  • 시카고 공장 : ThickCrustDough + 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

마무리

팩토리 메서드 패턴

  • 단일 메서드를 사용하여 하나의 제품을 생성
  • 일반적으로 하나의 추상 메서드를 포함하는 인터페이스나 추상 클래스를 정의
  • 각 구체적인 팩토리 클래스는 이 메서드를 구현하여 특정 객체를 생성

추상 팩토리 패턴

  • 여러 메서드를 사용하여 관련된 여러 제품을 생성
  • 여러 개의 추상 메서드를 포함하는 인터페이스나 추상 클래스를 정의
  • 각 구체적인 팩토리 클래스는 이 메서드들을 구현하여 관련된 여러 객체를 생성

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

Want to know more about 'The basic factory pattern'? Check out "The Factory Pattern: Basics"!

Types of Factory Patterns

The pattern we looked at earlier is the most basic structure, known as the SimpleFactory pattern. This time, let’s look at the other two patterns.

  1. MethodFactory
  2. AbstractFactory

The Method Factory Pattern

It encapsulates object creation logic in subclasses, which gives you flexibility and extensibility. It defines an interface for creating objects, but lets the subclasses decide which class to instantiate.

The Situation

Our pizza shop has been doing incredibly well. Let’s say we’re opening a branch in every city. Even for the same menu item, each branch gets ingredients processed at the factory with a local twist, and then delivered.

Pizza in every city

The Code

First, let’s make a pizza for each region. We’ll also create an enum type to use when branching on the pizza type.

from abc import ABC

class Pizza(ABC):
    name = "Pizza"

class NYStyleCheesePizza(Pizza):
    name = "New York Cheese Pizza"

class ChicagoStyleCheesePizza(Pizza):
    name = "Chicago Cheese Pizza"
from enum import StrEnum

class PizzaType(StrEnum):
    CHEESE = "cheese"

Now let’s write the most important part: the factory class. The heart of this pattern is that the interface declares an object creation method, and the subclasses implement that method.

In other words, you can have several factory classes, and each one can add whatever implementation it wants.

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("Chicago pizza is cut into squares.")

Now the client that uses the factory just receives the right factory object for each situation through injection. Want to open a New York branch? Pass in the New York factory to make pizza. Want a Chicago branch? Pass in the Chicago factory.

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)

Even the same cheese pizza can take on different characteristics depending on the factory.

You ordered a New York Cheese Pizza.

Chicago pizza is cut into squares.
You ordered a Chicago Cheese Pizza.

Class Diagram: 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

The Abstract Factory pattern is a creational pattern that provides an interface for creating families of related objects. Its defining trait is that it groups together and creates objects that are related to or depend on each other.

The Situation

Let’s think about factories that make each ingredient a pizza needs. A factory makes the dough and the cheese for a pizza, and each factory makes a different dough and cheese.

  • The New York factory makes ThinCrustDough and ReggianoCheese.
  • The Chicago factory makes ThickCrustDough and MozzarellaCheese.

Each branch simply makes pizza with the ingredients it gets from its factory.

Pizza factories

The Code

First, let’s create each ingredient. We define dough and cheese in abstract form and implement them in concrete ingredient classes.

  • Dough : ThinCrustDough / ThickCrustDough
  • Cheese : ReggianoCheese / MozzarellaCheese
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"

Next we need a factory interface that declares a method for creating each ingredient object. Think of it as declaring one abstract method per category. The concrete factories then implement related or interdependent objects for each category.

Here, each factory picks one ingredient per category and bundles them together.

  • New York factory : ThinCrustDough + ReggianoCheese
  • Chicago factory : ThickCrustDough + MozzarellaCheese
from abc import ABC, abstractmethod

class AbstractFactory(ABC):
    name = "Factory"

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

    @abstractmethod
    def make_cheese(self) -> Cheese:
        pass
class NYPizzaFactory(AbstractFactory):
    name = "the New York factory"

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

    def make_cheese(self) -> Cheese:
        return ReggianoCheese()
class ChicagoPizzaFactory(AbstractFactory):
    name = "the Chicago factory"

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

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

Now the client can get a set of related ingredients through each factory. With cheese and dough from the New York factory, it makes a New York-style pizza! With cheese and dough from the Chicago factory, it makes a Chicago-style pizza!

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"Using ingredients from {self.factory.name},")
        print(f"making a pizza with {cheese} cheese and {dough} 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()
Using ingredients from the New York factory,
making a pizza with ReggianoCheese cheese and ThinCrustDough dough.
Using ingredients from the Chicago factory,
making a pizza with MozzarellaCheese cheese and ThickCrustDough dough.

Class Diagram: 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

Wrapping Up

The Factory Method Pattern

  • Creates a single product using a single method
  • Typically defines an interface or abstract class containing one abstract method
  • Each concrete factory class implements this method to create a specific object

The Abstract Factory Pattern

  • Creates several related products using several methods
  • Defines an interface or abstract class containing several abstract methods
  • Each concrete factory class implements these methods to create several related objects

You can think of an abstract factory as something that actually contains several “factory methods.”

python 디자인 패턴 팩토리

tiaz0128

Eat Sleep Coding.

Never Never GiveUp.

Security  |  BackEnd  |  Multi Cloud