본문 바로가기
프로그래밍/디자인패턴

빌더(Builder) 패턴

by bantomak 2024. 4. 24.

🤔문제

많은 필드와 중첩된 객체들을 힘들게 단계별로 초기화해야 하는 복잡한 객체를 상상해 보자. 이러한 초기화 코드는 일반적으로 많은 매개변수가 있는 괴물 같은 생성자 내부에 묻혀 있다. 또, 더 최악의 상황에는 클라이언트 코드 전체에 흩어져 있을 수도 있다.

예를 들어 House(집) 객체를 만드는 방법에 대해 생각해 보자. 간단한 집을 지으려면 네 개의 벽과 바닥을 만든 후 문도 설치하고 한 쌍의 창문도 맞춘 후 지붕도 만들어야 한다. 하지만 뒤뜰과 기타 물품(난방 시스템, 배관 및 전기 배선 등)이 있는 더 크고 현대적인 집을 원하면 어떻게 해야 할까?

 

위 문제의 가장 간단한 해결책은 기초 House 클래스를 확장하고 매개변수의 모든 조합을 포함하는 자식 클래스들의 집합을 만드는 것이다. 그러나 결국 상당한 수의 자식 클래스를 만들게 될 것이다. 새로운 매개변수(예: 현관 스타일)를 추가할 때마다 이 계층구조는 훨씬 더 복잡해질 것이다.

 

자식 클래스들을 늘리지 않는 다른 접근 방식이 있다. 기초 House 클래스에 House 객체를 제어하는 모든 가능한 매개변수를 포함한 거대한 생성자를 만드는 방법이다. 이 접근 방식은 실제로 자식 클래스들의 필요성을 제거하나, 다른 문제를 만들어 낸다.

꽤나 익숙한 생성자의 모습이다. 프로그래머라면 누구나 만나봤을 코드의 모습

 

보통 대부분의 매개변수가 사용되지 않아 생성자 호출들의 코드가 매우 못생겨질 것이다.

 

😊해결책

빌더 패턴은 자신의 클래스에서 객체 생성 코드를 추출하여 builders(건축업자들)라는 별도의 객체들로 이동하도록 제안한다.

이 패턴은 객체 생성을 일련의 단계들로 정리하며, 객체를 생성하고 싶으면 위 단계들을 builder(빌더) 객체에 실행하면 된다. 또 중요한 점은 모든 단계를 호출할 필요가 없다는 것으로, 객체의 특정 설정을 제작하는 데 필요한 단계들만 호출하면 된다.

 

일부 건축 단계들은 제품의 다양한 표현을 건축해야 하는 경우 다른 구현들이 필요할 수 있다. 예를 들어, 오두막의 벽은 나무로 지을 수 있지만 성벽은 돌로 지어야 한다. 이런 경우 같은 건축 단계들의 집합을 다른 방식으로 구현하는 여러 다른 빌더 클래스를 생성할 수 있으며, 그런 다음 건축 프로세스(즉, 건축 단계에 대한 순서화된 호출들의 집합)내에서 이러한 빌더들을 사용하여 다양한 종류의 객체를 생성할 수 있다.

 

디렉터 (관리자)

더 나아가 제품을 생성하는 데 사용하는 빌더 단계들에 대한 일련의 호출을 디렉터(관리자)라는 별도의 클래스로 추출할 수 있다. 디렉터 클래스는 제작 단계들을 실행하는 순서를 정의하는 반면 빌더는 이러한 단계들에 대한 구현을 제공한다.

프로그램에 디렉터 클래스를 포함하는 것은 필수사항은 아니다. 언제든지 클라이언트 코드에서 생성 단계들을 직접 특정 순서로 호출할 수 있다. 그러나 디렉터 클래스는 다양한 생성 루틴들을 배치하여 프로그램 전체에서 재사용할 수 있는 좋은 장소가 될 수 있다.

 

또한 디렉터 클래스는 클라이언트 코드에서 제품 생성의 세부 정보를 완전히 숨긴다. 클라이언트는 빌더를 디렉터와 연관시키고 디렉터와 생성을 시행한 후 빌더로부터 결과를 얻기만 하면 된다.

🏠구조

  1. 빌더 인터페이스는 모든 유형의 빌더들에 공통적인 제품 생성 단계들을 선언한다.
  2. 구상 빌더들은 생성 단계들의 다양한 구현을 제공합니다. 또 구상 빌더들은 공통 인터페이스를 따르지 않는 제품들도 생산할 수 있다.
  3. 제품들은 그 결과로 나온 객체들이다. 다른 빌더들에 의해 생성된 제품들은 같은 클래스 계층구조 또는 인터페이스에 속할 필요가 없다.
  4. 디렉터 클래스는 생성 단계들을 호출하는 순서를 정의하므로 제품들의 특정 설정을 만들고 재사용할 수 있다.
  5. 클라이언트는 빌더 객체들 중 하나를 디렉터와 연결해야 한다. 일반적으로 위 연결은 디렉터 생성자의 매개변수를 통해 한번만 수행되며, 그 후 디렉터는 모든 추가 생성에 해당 빌더 객체들을 사용한다. 그러나 클라이언트가 빌더 객체를 디렉터의 프로덕션 메서드에 전달할 때를 위한 대안적 접근 방식이 있다. 이 경우 디렉터와 함께 무언가를 만들 때마다 다른 빌더를 사용할 수 있다.

💡적용

'점층적 생성자'를 제거하기 위하여 빌더 패턴을 사용하라.

10개의 선택적 매개변수가 있는 생성자가 있다고 가정하자. 이렇게 복잡한 생성자를 호출하는 것은 매우 불편하다. 따라서 이 생성자를 오버로드하고 더 적은 수의 매개변수들을 사용하는 더 짧은 생성자 버전들을 여러 개 만든다. 이러한 생성자들은 여전히 주 생성자를 참조하며, 생략된 매개변수들에 일부 기본값들을 전달한다.

빌더 패턴을 사용하면 실제로 필요한 단계들만 사용하여 단계별로 객체들을 생성할 수 있으며, 패턴을 구현한 후에는 더 이상 수십 개의 매개변수를 생성자에 집어넣을 필요가 없다.

 

빌더 패턴은 당신의 코드가 일부 제품의 다른 표현들을 생성할 수 있도록 하고 싶을 때 사용하자.

 

빌더 패턴은 제품의 다양한 표현의 생성 과정이 세부 사항만 다른 유사한 단계를 포함할 때 적용할 수 있다. 기초 빌더 인터페이스는 가능한 모든 생성 단계들을 정의하고 구상 빌더들은 이러한 단계들을 구현하여 제품의 여러 표현을 생성한다. 또 한편 디렉터 클래스는 건설 순서를 안내한다.

 

빌더를 사용하여 복합체 트리들 또는 기타 복잡한 객체들을 생성하라.

빌더 패턴을 사용하면 제품들을 단계별로 생성할 수 있으며, 또 최종 제품을 손상하지 않고 일부 단계들의 실행을 연기할 수 있다. 그리고 재귀적으로 단계들을 호출할 수도 있는데, 이는 객체 트리를 구축해야 할 때 매우 유용하다.

 

빌더는 생성 단계들을 수행하는 동안 미완성 제품을 노출하지 않으며, 이는 클라이언트 코드가 불완전한 결과를 가져오는 것을 방지한다.

👍장단점

  • 객체들을 단계별로 생성하거나 생성 단계들을 연기하거나 재귀적으로 단계들을 실행할 수 있다.
  • 제품들의 다양한 표현을 만들 때 같은 생성 코드를 재사용할 수 있다.
  • 단일 책임 원칙. 제품의 비즈니스 로직에서 복잡한 생성 코드를 고립시킬 수 있다.
  • 패턴이 여러 개의 새 클래스들을 생성해야 하므로 코드의 전반적인 복잡성이 증가한다.

🔍다른 패턴관의 관계

  • 많은 디자인은 복잡성이 낮고 자식 클래스들을 통해 더 많은 커스터마이징이 가능한 팩토리 메서드로 시작해 더 유연하면서도 더 복잡한 추상 팩토리, 프로토타입 또는 빌더 패턴으로 발전해 나간다.
  • 빌더는 복잡한 객체들을 단계별로 생성하는데 중점을 둔다. 추상 팩토리는 관련된 객체들의 패밀리들을 생성하는데 중점을 둔다. 추상 팩토리는 제품을 즉시 반환하지만 빌더는 제품을 가져오기 전에 당신이 몇 가지 추가 생성 단계들을 실행할 수 있도록 한다.
  • 복잡한 복합체 패턴 트리를 생성할 때 빌더를 사용할 수 있다. 왜냐하면 빌더의 생성 단계들을 재귀적으로 작동하도록 프로그래밍할 수 있기 때문이다.
  • 빌더브리지와 조합할 수 있다. 디렉터 클래스는 추상화의 역할을 하고 다양한 빌더들은 구현의 역할을 한다.
  • 추상 팩토리들, 빌더프로토타입들은 모두 싱글턴으로 구현할 수 있다.

참고 사이트

 

리팩터링과 디자인 패턴

Hello, world! Refactoring.Guru는 리팩토링, 디자인 패턴, SOLID 원칙 및 기타 스마트 프로그래밍 주제에 대해 알아야 할 모든 것을 쉽게 찾을 수 있는 자원입니다. 이 사이트에서는 이러한 모든 주제가 어

refactoring.guru

'프로그래밍 > 디자인패턴' 카테고리의 다른 글

싱글톤(Singleton) 패턴  (1) 2024.04.30
프로토타입(Prototype) 패턴  (1) 2024.04.30
추상 팩토리(Abstract Factory) 패턴  (1) 2024.04.24
팩토리 메서드(Factory Method) 패턴  (0) 2024.04.23
OOP의 기초  (1) 2024.04.19

댓글