HanSol's Oak Cask

Builder 패턴 본문

디자인 패턴/생성 패턴

Builder 패턴

HanSol_Lim 2025. 3. 5. 16:01

 1. Builder 패턴이란?

Builder 패턴은 객체 생성 과정이 복잡할 때, 단계적으로 객체를 생성할 수 있도록 도와주는 디자인 패턴입니다.
즉, 객체의 생성 과정과 표현 방식을 분리하여, 가독성과 유지보수성을 높이는 목적으로 사용됩니다.


2. 왜 Builder 패턴을 사용할까?

1) 생성자(Constructors) 매개변수 난잡함 문제 해결

  • 생성자에 많은 매개변수가 필요하면 매개변수 순서 실수가 발생할 가능성이 높음.
  • 일부 값만 설정하고 싶은 경우 불필요한 null 값을 넣어야 하는 문제가 발생.

🚨 문제 코드 (생성자 사용)

class Car {
    private String brand;
    private String model;
    private int year;
    private boolean sunroof;
    private boolean navigation;

    public Car(String brand, String model, int year, boolean sunroof, boolean navigation) {
        this.brand = brand;
        this.model = model;
        this.year = year;
        this.sunroof = sunroof;
        this.navigation = navigation;
    }
}
  • 🚨 문제점:
    • Car 객체를 생성하려면 모든 인자를 기억해서 넣어야 함.
    • 일부 옵션만 넣고 싶어도 null이나 기본값을 수동으로 지정해야 함.
    • 매개변수 순서를 실수하면 컴파일 오류는 발생하지 않지만, 잘못된 값이 들어가는 위험이 있음.

2) 생성자 오버로딩(Overloading) 문제 해결

  • 다양한 조합의 객체를 만들기 위해 생성자 오버로딩을 많이 사용하면 코드가 길어지고 유지보수가 어려움.

🚨 문제 코드 (생성자 오버로딩)

class Car {
    private String brand;
    private String model;
    private int year;
    private boolean sunroof;
    private boolean navigation;

    public Car(String brand, String model) {
        this(brand, model, 2023, false, false);
    }

    public Car(String brand, String model, int year) {
        this(brand, model, year, false, false);
    }
}
  • 🚨 문제점:
    • 생성자 조합이 많아지면 코드가 길어지고 유지보수가 어려움.
    • 새로운 필드가 추가될 경우 모든 생성자를 수정해야 하는 불편함 발생.

3. Builder 패턴 적용 코드

🎯 Builder 패턴을 적용하면, 복잡한 객체 생성을 유연하고 직관적으로 만들 수 있음.

(1) Builder 패턴 적용 코드

class Car {
    private String brand;
    private String model;
    private int year;
    private boolean sunroof;
    private boolean navigation;

    public static class Builder {
        private String brand;
        private String model;
        private int year = 2023;
        private boolean sunroof = false;
        private boolean navigation = false;

        public Builder(String brand, String model) {
            this.brand = brand;
            this.model = model;
        }

        public Builder year(int year) {
            this.year = year;
            return this;
        }

        public Builder sunroof(boolean sunroof) {
            this.sunroof = sunroof;
            return this;
        }

        public Builder navigation(boolean navigation) {
            this.navigation = navigation;
            return this;
        }

        public Car build() {
            return new Car(this);
        }
    }

    private Car(Builder builder) {
        this.brand = builder.brand;
        this.model = builder.model;
        this.year = builder.year;
        this.sunroof = builder.sunroof;
        this.navigation = builder.navigation;
    }
}

(2) Lombok을 활용한 간결한 Builder 패턴

import lombok.Builder;
import lombok.ToString;

@Builder // Lombok이 자동으로 Builder 패턴 적용
@ToString
class Car {
    private String brand;
    private String model;
    private int year;
    private boolean sunroof;
    private boolean navigation;
}

4. Builder 패턴의 단점

1) 코드의 복잡성이 증가

  • Builder 패턴을 적용하면 기본적인 생성자보다 코드량이 증가할 수 있음.
  • 대안: Lombok을 사용하면 코드 길이를 줄일 수 있음.

2) 객체 생성 비용이 증가

  • Builder 패턴은 매번 새로운 객체를 생성해야 하므로, 성능이 중요한 애플리케이션에서는 불리할 수 있음.
  • 대안: 객체 풀링(Object Pooling) 기법을 활용할 수 있음.

3) 필수 값 검증이 어려움

  • 생성자는 필수 매개변수를 강제할 수 있지만, Builder 패턴은 필수 필드 없이도 객체가 생성될 위험이 있음.
  • 대안: Builder 내부에서 필수 필드를 검증하는 로직 추가.
@Builder
class Car {
    private String brand;
    private String model;

    public static class CarBuilder {
        public Car build() {
            if (brand == null || model == null) {
                throw new IllegalStateException("Brand and model are required fields!");
            }
            return new Car(brand, model);
        }
    }
}

5. 결론

🎯 Builder 패턴을 활용하면 복잡한 객체 생성을 쉽게 관리할 수 있지만, 코드 복잡성 증가와 객체 생성 비용 증가 등의 단점도 고려해야 한다.
✅ Lombok을 활용하면 Builder 패턴을 더욱 간결하게 적용할 수 있음.
🚀 즉, 객체 생성이 복잡해질수록 Builder 패턴을 활용하면 코드가 더 명확해진다!