빌더 패턴(Builder Pattern)

DongGyun Cho·2023년 3월 11일

Java/Spring/Annotation

목록 보기
4/7

출처:

빌더 패턴

생성과 관련된 디자인 패턴으로, 동일한 프로세스를 다른 표현 결과로 만들 수 있게 하는 패턴이다.

결과적으로는 생성자를 가독성 좋게 만들어주는 도구라고 할 수 있을것 같다.

일반적으로 객체를 생성하는 방법은 2가지 방법이 많이 사용되어 왔다

  1. Setter를 이용하는 자바빈 패턴

Setter 를 사용하게 된다면, 객체 하나를 만들기 위해서 함수를 여러개 호출해야되고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 됩니다.

그리고 Java Bean 규약에 따르면 Setter는 public으로 어떤 곳에서나 변경이 가능합니다.
따라서 객체의 일관성을 유기하기가 힘듭니다.

한 번 값이 설정 되었을 때, 변경을 막기 위해 final 키워드를 사용해서 제한을 할 수도 있지만, 애초에 final로 제한을 할 거 였다면 생성자에서 값을 설정하고, Setter 사용을 하지 않는게 맞다고 생각됩니다.

  1. 생성자에 인자를 넣어 인스턴스를 생성하는 방법

생성자를 사용했을 때 문제점은 가독성입니다.

물론 이러한 가독성 문제는 Lombok에 힘을 빌려 처리할 수 있지만, 만약 해당 클래스에서 몇개의 필드값들이 필수가 아닌경우는 어떻게 될까요?

우리는 그 때마다 새로운 생성자 함수를 만들어 줘야하고, 객체를 생성할 때에도 순서에 맞게 넣어주기위해 계속적으로 확인을 해야합니다.

그래서 빌더 패턴

그래서 우리는 이제 빌더 패턴을 사용해보기로 하겠습니다.

public class User {

  private Long id;
  private String email;
  private String password;
  private String name;

  public User(Long id, String email, String password) {
    this.id = id;
    this.email = email;
    this.password = password;
  }

  ...getter 함수

  // ✨ 빌더 패턴을 위한 빌더 클래스!
  static public class Builder {
    private Long id;
    private String email;
    private String password;

    public Builder() {
    }

    public Builder(User user) {
      this.id = user.id;
      this.email = user.email;
      this.password = user.password;
    }

    public Builder id(Long id) {
      this.id = id;
      return this;
    }

    public Builder email(String email) {
      this.email = email;
      return this;
    }

    public Builder password(String password) {
      this.password = password;
      return this;
    }

    public User build() {
      return new User(id, email, password);
    }
  }
}

위와 같이 클래스 내부에서 builder 클래스를 따로 정의하여 사용할 수 있습니다.

각 함수들을 보면 알 수 있듯, 값을 설정하고 자기자신을 반환하기 때문에 함수를 연속적으로 체이닝 하듯이 사용할 수 있습니다.

아래 코드는 생성자와 빌더패턴을 사용한 사용법 및 비교 예제입니다.

class UserTest {

  @Test
  @DisplayName("빌더와 생성자 객체가 동일하다!")
  public void 빌더_테스트() {
    String email = "test@email.com";
    String password = "1234";
    String name = "dooly";

    // 🤔 생성자 사용
    User user = new User(null, email, password, name);

    // ✨ 빌더 패턴 이용
    // ✨ 어떤 값을 설정 하는지 명확하게 알 수 있고!
    // ✨ 필요한 값만 정의한다면, 나머지는 null로 자동 설정 가능합니다.
    User build = new User.Builder()
            .email(email)
            .password(password)
            .name(name)
            .build();

    assertThat(build.getEmail()).isEqualTo(user.getEmail());        // 결과값: OK! 👍
    assertThat(build.getPassword()).isEqualTo(user.getPassword());  // 결과값: OK! 👍
    assertThat(build.getName()).isEqualTo(user.getName());          // 결과값: OK! 👍
  }
}

위와 같이 빌더 패턴을 사용해서 필요한 값만 설정 가능하고, 어떤 값을 설정하는지 명확하게 알 수 있기 때문에 가독성을 높일 수 있습니다.

하지만 이러한 클래스 빌더 조차 불편해 할 까봐 Lombok에서는 @Builder 어노테이션을 제공하고있습니다.

빌더 패턴 with Lombok

Lombok을 활용한 빌더패턴은 두 가지 방법이 있습니다.

  • 클래스 전체 Builder 적용
  • 특정 생성자에게만 Builder 적용
  • 클래스 전체 Builder 적용
@Getter @Builder // ✨ 클래스 전체 필드를 빌더로 사용 가능!
public class UserLombok {

  private Long id;
  private String email;
  private String password;
  private String name;
}

// 사용예제
public User join(String email, String password, String name) {
  UserLombok build = UserLombok.builder()
            .email(email)
            .password(password)
            .name(name)
            .build();
  ...
}
  • 특정 생성자에서만 Builder 적용
@Getter
public class UserLombok {

  private Long id;
  private String email;
  private String password;
  private String name;

  @Builder // ✨ 빌더는 email, password만 사용 가능
  public UserLombok(String email, String password) {
    this.email = email;
    this.password = password;
  }

  public UserLombok(Long id, String email, String password, String name) {
    this.id = id;
    this.email = email;
    this.password = password;
    this.name = name;
  }
}

// 사용예제 - email, password만 가능!
public User join(String email, String password, String name) {
  UserLombok build = UserLombok.builder()
            .email(email)
            .password(password)
            .build();
  ...
}
profile
끈기를 가지고 해보자.

0개의 댓글