개발/Lombok

@Builder

함수형 인간 2025. 3. 11. 17:57
 

@Builder는 롬복(Lombok)에서 제공하는 어노테이션으로, 디자인 패턴 중 하나인 빌더 패턴(Builder Pattern)을 자동으로 생성해준다.

빌더 패턴이란?

객체 생성 과정이 복잡하거나, 선택적 매개변수가 많을 때 유용한 디자인 패턴. 객체를 직접 생성하는 대신, 빌더 객체를 통해 단계별로 속성 값을 설정하고 마지막에 최종 객체를 생성하는 방식.

@Builder의 주요 기능:

  • 빌더 클래스 자동 생성: @Builder를 클래스, 생성자, 또는 정적 메서드에 붙이면 롬복이 자동으로 빌더 클래스를 생성.
  • builder() 메서드 제공: 생성된 빌더 클래스의 인스턴스를 반환하는 builder() 정적 메서드를 자동으로 생성.
  • 필드 설정 메서드: 빌더 클래스 내에 각 필드에 대한 설정 메서드(setter와 유사하지만 메서드 체이닝을 지원)를 자동으로 생성.
  • build() 메서드: 설정이 완료된 객체를 생성하는 build() 메서드를 빌더 클래스에 자동으로 생성.
  • 메서드 체이닝 지원: 필드 설정 메서드들이 this (빌더 객체 자신)를 반환하므로, 메서드 체이닝을 통해 간결하게 객체를 생성.
  • @Singular과 함께 사용하여 컬렉션 처리 (선택 사항): List, Set, Map과 같은 컬렉션 필드에 @Singular 어노테이션을 함께 사용하면, 빌더에서 컬렉션에 요소를 하나씩 추가하는 메서드(add 접두사)를 제공.
  • @Builder.Default를 사용하여 기본 값 할당 (선택사항, 롬복 1.18.2 이상): 필드 선언시 초기화하는 값이 builder에서도 기본 값으로 유지되게 함.

동작 방식:

  1. @Builder 어노테이션이 붙은 클래스(또는 생성자, 정적 메서드)를 롬복이 인식.
  2. 롬복은 해당 클래스 내부에 [ClassName]Builder라는 이름의 빌더 클래스를 생성. (클래스에 적용했을 경우)
  3. 빌더 클래스에는 원본 클래스의 각 필드에 대한 private 필드가 생성.
  4. 빌더 클래스에는 각 필드에 값을 설정하는 메서드(메서드 체이닝 지원)가 생성.
  5. 빌더 클래스에는 build() 메서드가 생성되어, 설정된 값을 바탕으로 원본 클래스의 객체를 생성하여 반환.
  6. 원본 클래스에 builder() 정적 메서드가 생성되어, 빌더 클래스의 인스턴스를 반환.

사용 예시 (클래스에 적용):

Java
 
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;

import java.util.List;

@Builder
@Getter // Getter는 빌더와는 별개이지만, 생성된 객체의 값을 확인하기 위해 자주 함께 사용됩니다.
public class User {
    private String name;
    private int age;
    @Singular
    private List<String> hobbies;
    @Builder.Default
    private boolean active = true;
}

// 객체 생성 예시:
public class Main {
    public static void main(String[] args) {
        User user = User.builder()
                .name("John Doe")
                .age(30)
                .hobby("reading") // @Singular 덕분에 add + 필드명 형태
                .hobby("coding")  // 여러개 추가 가능.
                // .active(false) // @Builder.Default에 의해 true가 기본값이므로 생략가능.
                .build();

        System.out.println(user.getName());
        System.out.println(user.getHobbies());
        System.out.println(user.isActive());
    }
}

사용 예시 (생성자에 적용):

Java
 
import lombok.Builder;
import lombok.Getter;

@Getter
public class Product {
    private String name;
    private double price;
    private int stock;

    @Builder // 생성자에 적용
    public Product(String name, double price, int stock) {
        this.name = name;
        this.price = price;
        this.stock = stock;
    }
}

// 객체 생성 예시:
// Product.builder()...  (생성자에 @Builder를 적용해도 동일하게 사용)

사용 예시 (정적 팩토리 메서드에 적용):

Java
 
import lombok.*;

@Value
public class Mail {
    private final String recipient;
    private final String title;
    private final String body;

    @Builder(builderMethodName = "create") //정적 팩토리 메서드 이름과 다르게 설정
    public static Mail of(String recipient, String title, String body) {
        return new Mail(recipient, title, body);
    }
}

//객체 생성
//Mail mail = Mail.create()...

장점:

  • 가독성 향상: 객체 생성 코드가 훨씬 명확해지고, 어떤 필드에 어떤 값이 설정되는지 한눈에 파악하기 쉽다.
  • 유연성 증가: 선택적 매개변수가 많은 경우에도 깔끔하게 객체를 생성할 수 있다.
  • 불변성(Immutability) 지원: 빌더 패턴은 불변 객체를 생성하는 데 유용. (모든 필드를 final로 선언하고, setter를 제공하지 않으면 불변 객체가 된다.)
  • 코드 중복 감소: 빌더 클래스 생성 및 관련 메서드 작성을 롬복이 대신해주므로, 코드 양이 줄어든다.
  • 유지보수 용이: 필드가 추가/삭제되더라도 빌더 관련 코드는 롬복이 자동으로 관리해주므로, 유지보수가 간편.

주의 사항:

  • 모든 필드를 빌더를 통해 설정해야 하는 것은 아닙니다. 필수적인 필드는 생성자에 직접 전달하고, 선택적인 필드만 빌더를 통해 설정하는 것이 더 좋은 설계일 수 있다.
  • 상속 관계에서 @Builder를 사용할 때는 주의가 필요. 하위 클래스에서 @Builder를 사용할 때 상위 클래스의 필드를 함께 빌드하려면 @SuperBuilder를 사용하거나, 상위 클래스의 빌더를 명시적으로 호출해야 한다.
  • 빌더 패턴 자체가 객체 생성 로직을 분리하는 것이므로, 객체 생성 과정이 매우 단순한 경우에는 오히려 코드가 더 복잡해질 수 있다. 이런 경우에는 빌더 패턴을 사용하지 않는 것이 더 나을 수 있다.