Skip to content

builder-pattern

TL;DR

  • 복잡한 객체의 생성 과정을 단계별로 나누어 처리하며, 동일한 생성 절차에서 서로 다른 표현을 만들 수 있도록 하는 패턴.
  • 클라이언트 입장에서 생성 과정을 알아야하면 Builder. 생성 과정을 숨기고 싶으면 Factory.

활용성

  • 복잡한 객체를 생성해야 하고, 다양한 구성 요소가 조합되어 객체를 완성해야 할 때.
  • 동일한 객체 생성 과정에서 서로 다른 표현을 만들고자 할 때.
  • 객체 생성 과정에서 유연성과 가독성을 높이고자 할 때.

결과

  • 장점

    • 복잡한 객체 생성의 분리: 객체 생성 코드를 분리하여 객체의 생성 과정이 코드의 다른 부분과 분리됨
    • 가독성 향상: 단계별로 객체를 생성하므로 코드의 가독성과 유지보수성이 향상
    • 다양한 표현 가능: 빌더를 통해 동일한 객체 생성 절차에서 서로 다른 표현을 만들 수 있음
  • 단점

    • 설계 복잡성 증가: 간단한 객체 생성에는 불필요할 수 있으며, 코드 복잡성을 증가시킴
    • 가변성 증가: 객체의 다양한 표현을 지원하기 위해 여러 개의 빌더가 필요할 수 있어 관리가 복잡해짐

구현

  • Director: 객체 생성의 절차를 관리. 빌더에게 객체 생성을 지시하며, 각 단계의 실행 순서를 제어
  • Builder: 객체 생성을 위한 인터페이스를 제공. 이 인터페이스를 구현한 구체적인 빌더들이 실제 객체를 구성
  • Concrete Builder: Builder 인터페이스를 구현하여 구체적인 객체를 생성. 각 빌더는 특정한 객체 생성을 책임짐
  • Product: 최종적으로 생성되는 복잡한 대상 객체
Code Example

예시

from abc import ABC, abstractmethod

# Product 클래스: WebPage
class WebPage:
    def __init__(self):
        self.header: Header = None
        self.body: List[BodySection] = []
        self.footer: Footer = None

    def __str__(self):
        body_str = "\n".join([str(section) for section in self.body])
        return f"Header: {self.header}\n\nBody:\n{body_str}\n\nFooter: {self.footer}"

# 중첩된 클래스들: Header, BodySection, Footer
class Header:
    def __init__(self, logo, menu):
        self.logo = logo
        self.menu = menu

    def __str__(self):
        return f"Logo: {self.logo}, Menu: {', '.join(self.menu)}"

class BodySection:
    def __init__(self, title, content):
        self.title = title
        self.content = content

    def __str__(self):
        return f"Title: {self.title}, Content: {self.content}"

class Footer:
    def __init__(self, copyright_info, links):
        self.copyright_info = copyright_info
        self.links = links

    def __str__(self):
        return f"Copyright: {self.copyright_info}, Links: {', '.join(self.links)}"

# Builder 인터페이스
class WebPageBuilder(ABC):
    @abstractmethod
    def build_header(self, logo, menu):
        pass

    @abstractmethod
    def add_body_section(self, title, content):
        pass

    @abstractmethod
    def build_footer(self, copyright_info, links):
        pass

    @abstractmethod
    def get_webpage(self) -> WebPage:
        pass

# Concrete Builder
class ConcreteWebPageBuilder(WebPageBuilder):
    def __init__(self):
        self.webpage = WebPage()

    def build_header(self, logo, menu):
        self.webpage.header = Header(logo, menu)

    def add_body_section(self, title, content):
        self.webpage.body.append(BodySection(title, content))

    def build_footer(self, copyright_info, links):
        self.webpage.footer = Footer(copyright_info, links)

    def get_webpage(self) -> WebPage:
        return self.webpage

# Director
class WebPageDirector:
    def __init__(self, builder: WebPageBuilder):
        self._builder = builder

    def construct_home_page(self):
        self._builder.build_header("My Logo", ["Home", "About", "Contact"])
        self._builder.add_body_section("Welcome", "Welcome to our homepage!")
        self._builder.add_body_section("Features", "We offer a wide range of services.")
        self._builder.build_footer("© 2024 My Company", ["Privacy", "Terms of Service"])

    def construct_about_page(self):
        self._builder.build_header("My Logo", ["Home", "About", "Contact"])
        self._builder.add_body_section("About Us", "We are a leading company in our industry.")
        self._builder.add_body_section("Our Mission", "To deliver high-quality products.")
        self._builder.build_footer("© 2024 My Company", ["Careers", "Contact Us"])

# 사용 예시
if __name__ == "__main__":
    builder = ConcreteWebPageBuilder()
    director = WebPageDirector(builder)

    # 홈 페이지 생성
    director.construct_home_page()
    home_page = builder.get_webpage()
    print("Home Page:\n", home_page)

    # 어바웃 페이지 생성
    builder = ConcreteWebPageBuilder()  # 새 빌더로 초기화
    director = WebPageDirector(builder)
    director.construct_about_page()
    about_page = builder.get_webpage()
    print("\nAbout Page:\n", about_page)

요약

  • 빌더 패턴은 복잡한 객체를 생성할 때 유용한 패턴으로, 객체 생성 과정과 객체를 표현하는 방식을 분리할 수 있습니다.
  • 이 패턴을 사용하면 동일한 절차를 통해 다양한 객체를 생성할 수 있으며, 객체 생성 로직을 분리하여 코드의 가독성과 유지보수성을 높일 수 있습니다.
  • 그러나 간단한 객체 생성에는 오히려 복잡성을 증가시킬 수 있으므로, 필요에 따라 적절하게 사용하는 것이 중요합니다.

Director와 Factory의 차이점

  • DirectorFactory 객체와 비슷하게 느껴질 수 있지만, 역할과 목적이 다름.
  • 둘 다 객체 생성과 관련이 있지만, 그 방식과 사용하는 시나리오에서 중요한 차이가 있음

  • 목적:

    • Director
      • 빌더 패턴에서 Director의 주된 역할은 객체를 생성하는 과정(절차)을 정의하고, 그 절차에 따라 객체를 구성하는 것
      • Director는 객체 생성의 순서와 단계를 관리하지만, 객체 생성 자체의 논리보다는 객체가 어떻게 구성되는지에 중점을 둡니다.
    • Factory
      • Factory 패턴에서 Factory객체 생성의 책임을 캡슐화합니다.
      • 어떤 종류의 객체를 생성할지를 결정하고, 생성된 객체를 반환합니다.
      • Factory는 객체를 생성하는 로직 자체를 캡슐화하며, 클라이언트는 객체 생성의 복잡한 과정에 대해 알 필요가 없습니다.
  • 역할:

    • Director
      • 빌더 패턴의 Director는 여러 단계를 거쳐 복잡한 객체를 구성하는 데 사용
      • Director는 빌더에게 어떤 단계에서 어떤 작업을 수행할지를 지시하고, 각 단계를 거쳐 최종 객체를 완성 시킴 - Director는 빌더가 제공하는 인터페이스를 통해 작업을 수행하며, 객체가 생성되는 절차를 제어
    • Factory
      • Factory는 특정 유형의 객체를 생성하는 데 필요한 정보를 알고 있으며, 해당 객체를 직접 생성하여 반환
      • Factory는 객체의 생성 과정을 클라이언트 코드로부터 숨기고, 클라이언트는 단순히 Factory를 호출하여 객체를 받을 뿐
  • 사용 시나리오:

    • Director
      • 복잡한 객체를 구성하는 여러 단계를 관리할 필요가 있을 때
      • 예를 들어, 다양한 옵션이 있는 복잡한 제품을 만들거나, 여러 단계에 걸쳐 객체를 설정해야 할 때 사용
    • Factory
      • 객체를 생성하는 로직이 복잡하거나, 생성할 객체의 유형이 런타임에 결정될 때
      • 예를 들어, 구체적인 클래스를 미리 알 수 없고, 상황에 따라 다른 객체를 생성해야 할 때 Factory 패턴 사용
Factory vs Builder Code Example

1. Director 예시 (빌더 패턴)

# Builder 인터페이스
class CarBuilder(ABC):
    @abstractmethod
    def build_engine(self):
        pass

    @abstractmethod
    def build_wheels(self):
        pass

    @abstractmethod
    def build_frame(self):
        pass

    @abstractmethod
    def get_car(self) -> Car:
        pass

# Concrete Builder
class SportsCarBuilder(CarBuilder):
    def __init__(self):
        self.car = Car()

    def build_engine(self):
        self.car.engine = "V8 Engine"

    def build_wheels(self):
        self.car.wheels = "Sports Wheels"

    def build_frame(self):
        self.car.frame = "Lightweight Frame"

    def get_car(self) -> Car:
        return self.car

# Director
class CarDirector:
    def __init__(self, builder: CarBuilder):
        self._builder = builder

    def construct_sports_car(self):
        self._builder.build_frame()
        self._builder.build_engine()
        self._builder.build_wheels()
        return self._builder.get_car()

# 사용 예시
builder = SportsCarBuilder()
director = CarDirector(builder)
car = director.construct_sports_car()

여기서 Director는 객체 생성의 절차를 관리하고, Builder를 통해 단계를 수행

2. Factory 예시 (Factory 패턴)

class CarFactory:
    def create_car(self, car_type: str) -> Car:
        if car_type == "sports":
            return Car(engine="V8 Engine", wheels="Sports Wheels", frame="Lightweight Frame")
        elif car_type == "sedan":
            return Car(engine="V6 Engine", wheels="Standard Wheels", frame="Standard Frame")
        else:
            raise ValueError("Unknown car type")

# 사용 예시
factory = CarFactory()
sports_car = factory.create_car("sports")
sedan_car = factory.create_car("sedan")

여기서 Factory는 클라이언트가 요청한 유형의 객체를 직접 생성하고 반환