빌더 패턴(Builder Pattern)

유방현·2024년 11월 25일
0

1. 빌더 패턴이란?

빌더 패턴은 복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴입니다.

1.1 빌더 패턴의 핵심 개념

  • 복잡한 객체를 단계별로 생성
  • 동일한 생성 절차로 서로 다른 표현 결과를 만듦
  • 생성과정을 더 세밀하게 나눌 수 있음

2. 빌더 패턴이 필요한 이유

2.1 점층적 생성자 패턴(Telescoping Constructor Pattern)의 문제점

public class Pizza {
    private String dough;
    private String sauce;
    private String topping;
    
    // 생성자 1
    public Pizza(String dough) {
        this(dough, null);
    }
    
    // 생성자 2
    public Pizza(String dough, String sauce) {
        this(dough, sauce, null);
    }
    
    // 생성자 3
    public Pizza(String dough, String sauce, String topping) {
        this.dough = dough;
        this.sauce = sauce;
        this.topping = topping;
    }
}

이런 방식의 문제점:

  • 매개변수가 많아지면 생성자가 기하급수적으로 늘어남
  • 매개변수 순서를 혼동하기 쉬움
  • 선택적 매개변수가 많은 경우 처리가 어려움

2.2 자바빈즈 패턴(JavaBeans Pattern)의 문제점

public class Pizza {
    private String dough;
    private String sauce;
    private String topping;
    
    public Pizza() {}
    
    public void setDough(String dough) { this.dough = dough; }
    public void setSauce(String sauce) { this.sauce = sauce; }
    public void setTopping(String topping) { this.topping = topping; }
}

이런 방식의 문제점:

  • 객체 일관성(consistency)이 깨질 수 있음
  • 객체가 불변(immutable)이 될 수 없음
  • 스레드 안전성을 보장할 수 없음

3. 빌더 패턴 구현 방법

3.1 기본적인 빌더 패턴 구현

public class Pizza {
    private final String dough;
    private final String sauce;
    private final String topping;
    
    private Pizza(Builder builder) {
        this.dough = builder.dough;
        this.sauce = builder.sauce;
        this.topping = builder.topping;
    }
    
    public static class Builder {
        // 필수 매개변수
        private final String dough;
        
        // 선택 매개변수
        private String sauce = "";
        private String topping = "";
        
        public Builder(String dough) {
            this.dough = dough;
        }
        
        public Builder sauce(String sauce) {
            this.sauce = sauce;
            return this;
        }
        
        public Builder topping(String topping) {
            this.topping = topping;
            return this;
        }
        
        public Pizza build() {
            return new Pizza(this);
        }
    }
}

3.2 Lombok을 사용한 빌더 패턴

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class Pizza {
    private final String dough;
    private final String sauce;
    private final String topping;
}

4. 실전 예제

4.1 복잡한 주문 시스템 예제

public class Order {
    private final String customerName;
    private final String address;
    private final String phoneNumber;
    private final String email;
    private final String product;
    private final int quantity;
    private final boolean express;
    private final String specialInstructions;
    
    public static class Builder {
        // 필수 매개변수
        private final String customerName;
        private final String address;
        
        // 선택 매개변수 - 기본값으로 초기화
        private String phoneNumber = "";
        private String email = "";
        private String product = "";
        private int quantity = 1;
        private boolean express = false;
        private String specialInstructions = "";
        
        public Builder(String customerName, String address) {
            this.customerName = customerName;
            this.address = address;
        }
        
        public Builder phoneNumber(String val) {
            phoneNumber = val;
            return this;
        }
        
        public Builder email(String val) {
            email = val;
            return this;
        }
        
        public Builder product(String val) {
            product = val;
            return this;
        }
        
        public Builder quantity(int val) {
            quantity = val;
            return this;
        }
        
        public Builder express(boolean val) {
            express = val;
            return this;
        }
        
        public Builder specialInstructions(String val) {
            specialInstructions = val;
            return this;
        }
        
        public Order build() {
            return new Order(this);
        }
    }
    
    private Order(Builder builder) {
        customerName = builder.customerName;
        address = builder.address;
        phoneNumber = builder.phoneNumber;
        email = builder.email;
        product = builder.product;
        quantity = builder.quantity;
        express = builder.express;
        specialInstructions = builder.specialInstructions;
    }
}

사용 예제:

Order order = new Order.Builder("John Doe", "123 Street")
    .phoneNumber("123-456-7890")
    .email("john@example.com")
    .product("Laptop")
    .quantity(2)
    .express(true)
    .specialInstructions("Please deliver after 6 PM")
    .build();

5. 빌더 패턴의 장단점

5.1 장점

  1. 가독성이 좋음

    • 어떤 값을 설정하는지 명확하게 알 수 있음
    • 매개변수가 많아도 코드를 읽기 쉬움
  2. 유연성이 높음

    • 필요한 데이터만 설정 가능
    • 객체 생성 과정을 세밀하게 제어할 수 있음
  3. 불변성 확보

    • 객체를 불변으로 만들 수 있음
    • Thread-safe한 객체 생성 가능
  4. validating 용이

    • build() 메소드에서 유효성 검사를 수행할 수 있음

5.2 단점

  1. 코드량 증가

    • 빌더 클래스를 따로 만들어야 함
    • 클래스 파일이 많아짐
  2. 생성 비용

    • 객체 생성 시 빌더 객체도 함께 생성됨
    • 성능에 민감한 상황에서는 고려 필요

6. 자주 하는 질문들

Q1: 빌더 패턴은 언제 사용하면 좋나요?

A: 다음과 같은 경우에 빌더 패턴 사용을 고려해보세요:

  • 생성자 매개변수가 4개 이상일 때
  • 선택적 매개변수가 많을 때
  • 불변 객체를 만들어야 할 때

Q2: Lombok의 @Builder와 수동 구현 중 어떤 것이 좋나요?

A: 상황에 따라 다릅니다:

  • 단순한 경우: Lombok 사용
  • 복잡한 유효성 검사가 필요한 경우: 수동 구현
  • 커스텀 로직이 필요한 경우: 수동 구현

Q3: 빌더 패턴과 팩토리 패턴의 차이는 무엇인가요?

A:

  • 빌더: 복잡한 객체를 단계별로 생성
  • 팩토리: 객체 생성을 캡슐화하고 서브클래스에 위임
profile
코딩하는 직장인

0개의 댓글