@Builder

2 분 소요

이번 포스트는 업무 중 builder 어노테이션에 대하여 알게된 것이 있어 정리하고자 작성하겠습니다.

@Builder를 사용하다가 기본 생성자가 필요하게 되어 @NoArgsConstructor 어노테이션을 추가했더니 에러가 발생했습니다.

그 이유는 모든 멤버변수를 받는 생성자가 없기 때문이었는데요.
따라서 @AllArgsConstructor를 적용하니 정상적으로 작동하게 되었습니다.
왜 이러한 에러가 발생했는지 차근차근 알아보겠습니다.

먼저 Builder 어노테이션을 알아보기 전에 Builder Pattern이 무엇인지 짚고 넘어가겠습니다.

Builder 패턴이란

Builder 패턴은 복합 객체의 생성 과정과 표현 방법을 분리하여 동일한 생성 절차에서 서로 다른 표현 결과를 만들 수 있게하는 패턴입니다.
객체의 필드가 많은 경우 인자를 받는 생성자를 통해 객체를 생성하게 되면 어떤 필드에 어떤 값이 들어가는지 명확히 알기 힘들겠죠.
요즘은 IDE에서 자동완성 기능과 값을 넣을 필드가 무엇인지 표시해주기 때문에 이러한 단점이 보완되긴 했습니다.
하지만 개발자는 사람이기 때문에 항상 실수를 할 수 있고, 여러 개발자가 한 객체를 다룰 떄 생성자 인자의 순서를 바꾸는 일이 발생하면 어마무시한 에러를 경험하게 될 수도 있습니다.

public class Account {
    
    private Long id;

    private String email;

    private String password;

    public Account(String email, String password) {
        this.email = email;
        this.password = password;
    }
}

그럴 일이 많지는 않겠지만 위처럼 같은 타입의 인자를 받는 생성자의 인자 순서가 바뀌는 일이 발생하기라도 한다면 큰일이겠지요…
또한 기본 생성자로 객체를 생성하고 Setter를 통해 값을 주입하게 되면 엄청난 양의 Setter 코드와 객체의 불변성을 보장할 수 없다는 단점이 생깁니다.

Builder 패턴은 이러한 상황을 보완하며 아래와 같은 장점을 갖습니다.

  1. 객체 생성을 할 때 필요한 인자에 따라 유연하게 사용할 수 있다.
    • 어떠한 필드에 어떤 값을 넣는지 명확히 할 수 있고 값을 넣어줄 필요가 없는 필드는 굳이 선언하지 않아도 된다.
  2. 무조건적인 Setter를 방지하고 불변객체로 만들 수 있다.
  3. 필수 인자를 지정할 수 있다.

Builder 패턴은 Builder Pattern Example 페이지의 예제 코드를 통해 만들 수 있습니다.

하지만 모든 객체에 이런 코드를 넣는다는 것은 굉장히 불편한 일이지 않을까요??
Lombok은 이러한 불편을 해소하기 위해 @Builder를 제공하는데요, 어노테이션을 클래스, 생성자, Setter 메서드 위에 선언하면 Builder 코드를 만들어 줍니다.

클래스 위에 선언하면 기본적으로 모든 필드를 이용하여 Bulider 코드를 생성하며, 생성자가 선언되어 있지 않은 경우 Protected 수준의 모든 인자를 갖는 생성자를 생성합니다.
(@AllArgsConstructor(access = AccessLevel.PACKAGE)와 같습니다.)

만약 특정 필드를 갖는 생성자 위에 선언하면 lombok은 모든 필드를 갖는 생성자가 있다고 가정하며, 이 생성자를 통해 Builder 코드를 만들어 냅니다.

에러의 원인

Builder 어노테이션은 기본적으로 아무런 생성자가 없을 시 @AllArgsConstructor(access = AccessLevel.PACKAGE)를 적용한다고 언급했습니다.
따라서 저의 상황에서 기본생성자를 선언했기 때문에 롬복이 이미 생성자가 있다고 판단하고 인자를 갖는 생성자를 생성하지 않았고, Builder class를 생성하지 못한 것입니다.
그렇기 때문에 기본 생성자를 생성한 경우 인자를 갖는 생성자를 따로 만들어 주어야 이러한 에러를 피할 수 있습니다.

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public class Account {

    private Long id;

    private String email;

    private String password;
}

추가 - 상속 구조에서의 빌더패턴

@Builder
public class Account extends Parent {

    private Long id;

    private String email;

    private String password;
}

@Builder
public abstract class Parent {
    
    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

만약 위와 같은 구조의 객체가 있을 때, Account 클래스의 빌더로 BaseEntity의 값을 주입할 수 없습니다.
Account 객체는 부모 클래스의 필드가 private이기 때문에 접근할 수 없는데요.
이때 부모 객체에 대한 빌더 코드를 만들고 싶은 경우 사용할 수 있는 어노테이션이 @SuperBuilder입니다.

@SuperBuilder
public class Account extends Parent {

    private Long id;

    private String email;

    private String password;
}

@SuperBuilder
public abstract class Parent {
    
    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;
}

이번 포스트에서는 Lombok의 @Builder에 대하여 알아보았습니다.
항상 느끼는 것이지만 자주 사용하지만 제대로 알고 사용하지 않은 것들이 많다고 느껴지네요,,,
이번 기회로 저뿐 아니라 많은 분들이 Builder 어노테이션과 패턴에 대하여 확실히 잡고 가는 기회가 되길 바라겠읍니다.

참고: Lombok - @Builder

참고: 위키백과 - 빌더패턴

참고: 깃헙 - greekZorba

카테고리:

업데이트:

댓글남기기