
디자인 패턴 중 Builder 패턴에 대해 설명합니다.
개념
객체 생성 시 많은 파라미터가 존재하거나 생성과정이 복잡한 객체를 step by step으로 생성하는 생성패턴입니다. 다음과 같은 상황이라면 Builder 패턴을 고려할 수 있습니다.
- 객체 생성시 세부 사항은 다르지만 유사한 단계를 거치는 상황이 반복된다.
- 복잡한 세부 사항에 따른 객체의 성격을 보장해야 한다.
- 생성자 내부 코드가 거대해진다.
- telescoping constructor가 필요하다.
- 객체 생성 시 유효하지 않은 파라미터 혹은 파라미터 누락으로 인한 잠재적 에러 가능성 존재한다.
다음과 같은 장점이 있습니다.
- Single Responsibility Principle: 객체 생성에 대한 책임을 분리하여 비즈니스 로직의 과도한 책임을 분산합니다.
- builder는 객체를 완전하게 생성하기 전까지 노출하지 않으므로 완전한 객체 생성을 보장할 수 있어 잠재적 에러 가능성을 없앱니다.
- 복잡한 생성 과정이 심플해집니다.
자세히 살펴보겠습니다.

Builder
객체 생성 과정을 N개의 step 메소드로 정의합니다. 이 때 반환값을 자기자신(self)으로 하면 메소드 체이닝을 적용할 수 있습니다. 적절한 step 메소드 과정을 거친 후에 get_result 메소드로 객체를 얻을 수 있습니다.
Client
builder를 생성하여 step by step으로 객체를 생성합니다.
step 메소드 호출 순서가 중요한 객체 생성이 여러 번 반복되거나 step 메소드 호출등으로 여전히 객체 생성이 복잡하게 느껴진다면 Director 클래스를 정의하여 생성 과정을 위임할 수 있습니다.

Director
클라이언트로부터 step 메소드 세트 호출을 위임받아 수행합니다. builder를 생성자 파라미터로 전달받아 해당 builder의 step 메소드를 호출하거나 makeProduct1(builder) 같은 특정 객체를 생성하는 메소드를 정의하는 경우 메소드의 파라미터로 builder를 전달받습니다.
director가 정의될 때 어떤 객체가 반환될지 알 수 없어 최종 객체 전달은 builder가 담당하는데 만약 각 구현 builder들에서 상위 타입이 같은 객체를 반환한다면 director가 최종 객체 전달을 담당할 수 있습니다.
적용예시
피자를 만드는 애플리케이션을 개발 중이라고 가정합니다. 피자는 여러 재료가 많아 여러 파라미터들이 존재합니다.

이럴 때 Builder 패턴을 사용하면 단순 명료하게 피자 객체를 만들 수 있습니다.
class DoughEnum(enum.Enum):
THIN = 1
PAN = 2
SCREEN = 3
class SourceEnum(enum.Enum):
TOMATO = 1
CREAM = 2
class CheezeEnum(enum.Enum):
MOZZARELLA = 1
CHEDDAR = 2
class ToppingEnum(enum.Enum):
BEEF = 1
BELL_PEPPER = 2
PEPPERONI = 3
class Pizza:
def __init__(self) -> None:
self.size: int
self.dough: DoughEnum
self.source: SourceEnum
self.cheeze: CheezeEnum
self.topping: ToppingEnum
class PizzaBuilder:
def __init__(self) -> None:
self._pizza = Pizza()
def set_size(self, size: int) -> "PizzaBuilder":
self._pizza.size = size
return self
def set_dough(self, dough) -> "PizzaBuilder":
self._pizza.dough = dough
return self
def set_source(self, source) -> "PizzaBuilder":
self._pizza.source = source
return self
def set_cheeze(self, cheeze) -> "PizzaBuilder":
self._pizza.cheeze = cheeze
return self
def set_topping(self, topping) -> "PizzaBuilder":
self._pizza.topping = topping
return self
def build(self) -> Pizza:
if not hasattr(self._pizza, "size"):
raise
if not hasattr(self._pizza, "dough"):
raise
if not hasattr(self._pizza, "source"):
raise
if not hasattr(self._pizza, "cheeze"):
raise
if not hasattr(self._pizza, "topping"):
raise
return self._pizza
pb = PizzaBuilder()
pizza = pb.set_size(10).set_dough(DoughEnum.THIN).set_source(SourceEnum.TOMATO).set_cheeze(CheezeEnum.MOZZARELLA).set_topping(ToppingEnum.BEEF)
최종 객체를 반환하기 전 모든 재료가 추가되었는지 확인하여 반환합니다. 예제보다 나아가서 각 재료가 정상적인 재료인지 유효성 검사도 추가할 수 있고, 피자의 종류별로 Builder를 정의하여 해당 피자에 맞는 재료들이 추가되도록 보장할 수 있습니다.
참조
'객체 지향 > 디자인 패턴' 카테고리의 다른 글
디자인 패턴 - Adapter (0) | 2023.10.12 |
---|---|
디자인 패턴 - Singleton (0) | 2023.10.11 |
디자인 패턴 - Prototype (0) | 2023.10.09 |
디자인 패턴 - Abstact Factory (0) | 2023.10.05 |
디자인 패턴 - Factory Method (0) | 2023.10.03 |

디자인 패턴 중 Builder 패턴에 대해 설명합니다.
개념
객체 생성 시 많은 파라미터가 존재하거나 생성과정이 복잡한 객체를 step by step으로 생성하는 생성패턴입니다. 다음과 같은 상황이라면 Builder 패턴을 고려할 수 있습니다.
- 객체 생성시 세부 사항은 다르지만 유사한 단계를 거치는 상황이 반복된다.
- 복잡한 세부 사항에 따른 객체의 성격을 보장해야 한다.
- 생성자 내부 코드가 거대해진다.
- telescoping constructor가 필요하다.
- 객체 생성 시 유효하지 않은 파라미터 혹은 파라미터 누락으로 인한 잠재적 에러 가능성 존재한다.
다음과 같은 장점이 있습니다.
- Single Responsibility Principle: 객체 생성에 대한 책임을 분리하여 비즈니스 로직의 과도한 책임을 분산합니다.
- builder는 객체를 완전하게 생성하기 전까지 노출하지 않으므로 완전한 객체 생성을 보장할 수 있어 잠재적 에러 가능성을 없앱니다.
- 복잡한 생성 과정이 심플해집니다.
자세히 살펴보겠습니다.

Builder
객체 생성 과정을 N개의 step 메소드로 정의합니다. 이 때 반환값을 자기자신(self)으로 하면 메소드 체이닝을 적용할 수 있습니다. 적절한 step 메소드 과정을 거친 후에 get_result 메소드로 객체를 얻을 수 있습니다.
Client
builder를 생성하여 step by step으로 객체를 생성합니다.
step 메소드 호출 순서가 중요한 객체 생성이 여러 번 반복되거나 step 메소드 호출등으로 여전히 객체 생성이 복잡하게 느껴진다면 Director 클래스를 정의하여 생성 과정을 위임할 수 있습니다.

Director
클라이언트로부터 step 메소드 세트 호출을 위임받아 수행합니다. builder를 생성자 파라미터로 전달받아 해당 builder의 step 메소드를 호출하거나 makeProduct1(builder) 같은 특정 객체를 생성하는 메소드를 정의하는 경우 메소드의 파라미터로 builder를 전달받습니다.
director가 정의될 때 어떤 객체가 반환될지 알 수 없어 최종 객체 전달은 builder가 담당하는데 만약 각 구현 builder들에서 상위 타입이 같은 객체를 반환한다면 director가 최종 객체 전달을 담당할 수 있습니다.
적용예시
피자를 만드는 애플리케이션을 개발 중이라고 가정합니다. 피자는 여러 재료가 많아 여러 파라미터들이 존재합니다.

이럴 때 Builder 패턴을 사용하면 단순 명료하게 피자 객체를 만들 수 있습니다.
class DoughEnum(enum.Enum):
THIN = 1
PAN = 2
SCREEN = 3
class SourceEnum(enum.Enum):
TOMATO = 1
CREAM = 2
class CheezeEnum(enum.Enum):
MOZZARELLA = 1
CHEDDAR = 2
class ToppingEnum(enum.Enum):
BEEF = 1
BELL_PEPPER = 2
PEPPERONI = 3
class Pizza:
def __init__(self) -> None:
self.size: int
self.dough: DoughEnum
self.source: SourceEnum
self.cheeze: CheezeEnum
self.topping: ToppingEnum
class PizzaBuilder:
def __init__(self) -> None:
self._pizza = Pizza()
def set_size(self, size: int) -> "PizzaBuilder":
self._pizza.size = size
return self
def set_dough(self, dough) -> "PizzaBuilder":
self._pizza.dough = dough
return self
def set_source(self, source) -> "PizzaBuilder":
self._pizza.source = source
return self
def set_cheeze(self, cheeze) -> "PizzaBuilder":
self._pizza.cheeze = cheeze
return self
def set_topping(self, topping) -> "PizzaBuilder":
self._pizza.topping = topping
return self
def build(self) -> Pizza:
if not hasattr(self._pizza, "size"):
raise
if not hasattr(self._pizza, "dough"):
raise
if not hasattr(self._pizza, "source"):
raise
if not hasattr(self._pizza, "cheeze"):
raise
if not hasattr(self._pizza, "topping"):
raise
return self._pizza
pb = PizzaBuilder()
pizza = pb.set_size(10).set_dough(DoughEnum.THIN).set_source(SourceEnum.TOMATO).set_cheeze(CheezeEnum.MOZZARELLA).set_topping(ToppingEnum.BEEF)
최종 객체를 반환하기 전 모든 재료가 추가되었는지 확인하여 반환합니다. 예제보다 나아가서 각 재료가 정상적인 재료인지 유효성 검사도 추가할 수 있고, 피자의 종류별로 Builder를 정의하여 해당 피자에 맞는 재료들이 추가되도록 보장할 수 있습니다.
참조
'객체 지향 > 디자인 패턴' 카테고리의 다른 글
디자인 패턴 - Adapter (0) | 2023.10.12 |
---|---|
디자인 패턴 - Singleton (0) | 2023.10.11 |
디자인 패턴 - Prototype (0) | 2023.10.09 |
디자인 패턴 - Abstact Factory (0) | 2023.10.05 |
디자인 패턴 - Factory Method (0) | 2023.10.03 |