본문 바로가기
프로그래밍/디자인 패턴

[Design Pattern] Lombok에서 발견한 디자인패턴 - Builder Pattern

by 사바라다 2021. 2. 25.

안녕하세요. Lombok에 @Builder를 사용하면 객체 생성을 유연하게 할 수 있습니다. 이게 바로 빌더 패턴을 이용하는 건데요. 오늘은 이 빌터 패턴(Builder Pattern)에 대해서 알아보는 시간을 가져보도록 하겠습니다.

Effective Java의 두번째 규칙의 제목이 바로 생성자의 인자가 많을 때는 빌더(Builder) 패턴을 이용하라라고 되어있습니다. 빌더 패턴을 사용하면 객체의 생성을 깔끔하고 유연하게 할 수 있습니다. 어떻게 달성할 수 있다는 것일까요?

오늘은 빌더 패턴을 이용하면 어떻게 이를 달성할 수 있는지 다른 방법들과는 어떻게 다른지 한번 알아보도록 하겠습니다.

기존 생성자의 문제점

일반생성자는 선택적인 파라미터가 많은 상황에서 코드의 길이가 길어진다는 단점을 가지고 있습니다. 사람 클래스를 만드는 것으로 예를 한번 들어보도록 하겠습니다. 사람에게는 이름, 나이는 필수적인 요소입니다. 하지만 이메일, 전화번호는 없을 수 있습니다. 하지만 생성자를 만들때는 이런 부분을 명시하는것이 애매합니다. 아래 코드를 보겠습니다.

public class Person {

  private String name;

  private int age;

  private String email;

  private String phone;

  public Person(String name, int age, String email, String phone) {
      this.name = name;
      this.age = age;
      this.email = email;
      this.phone = phone;
  }
}

이렇게 클래스가 구성되어있을 때 각각의 경우에 대해서 사용한다면 아래와 같이 사용할 수 있습니다. 사용하지 않는 파라미터에 대해서는 null을 넣는다는 것으로 문제를 해결하고 있습니다. 이러한 방법은 가독성도 떨어질 뿐더러 null 값을 어떤 위치에 넣는지 순서에 대해 오류가 발생할 수도 있습니다.

public class Client {

    public void client() {
        Person person_1 = new Person("사바라다", 29, "koangho93@naver.com", "010-xxxx-xxxx");
        Person person_2 = new Person("사바라다", 29, null, "010-xxxx-xxxx");
        Person person_3 = new Person("사바라다", 29, "koangho93@naver.com", null);
        Person person_4 = new Person("사바라다", 29, null, null);
    }
}

점층정 생성자 패턴

이러한 1개의 생성자를 통한 초기화에 대한 문제를 해결하기 위해 가장 먼저 생각할 수 있는 방법이 점층적 생성자 패턴입니다. 이 패턴은 필수적인 파라미터가 있는 생성자를 만들고 선택적인 파라미터의 생성자를 하나씩 추가해서 만드는 패턴입니다. 아래와 깉이 선언합니다.

public class Person {

    private String name;

    private int age;

    private String email;

    private String phone;

    public Person(String name, int age) {
        this(name, age, null, null);
    }

    public Person(String name, int age, String email) {
        this(name, age, email, null); 
    }

    public Person(String name, int age, String email, String phone) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.phone = phone;
    }
}

또한 사용할때는 아래처럼 사용할 수 있습니다.

public class Client {

    public void client() {
        Person person_1 = new Person("사바라다", 29, "koangho93@naver.com", "010-xxxx-xxxx");
        Person person_2 = new Person("사바라다", 29, "koangho93@naver.com");
        Person person_3 = new Person("사바라다", 29);
    }
}

이러한 방식은 같은 타입의 파라미터를 가진다고 하면 곤란해질 수 있습니다. 아래의 예제를 보면 email과 phone 모두 String 형식입니다. 따라서 phone은 있으나 email은 없는 경우는 만들어내기 곤란합니다. 왜냐하면 이미 Person(String name, int age, String email)으로 email 일 경우가 차지하고 있기 때문입니다. 파라미터 이름만 다를 경우 오버라이딩이 되지 않으며 오류로 처리됩니다. 또한 이 방법의 경우 선택적 파라미터가 늘어나면 늘어날수록 생성자의 수도 많아져야하기 때문에 곤란해질 수 있습니다.

자바 빈(JavaBeans) 패턴

적용가능한 방법의 두번째는 자바 빈 패턴입니다. 이 패턴은 기본 생성자를 적용한 후 set 메서드를 통해서 값을 세팅해주는 것입니다. 이 방식은 점층적 생성자 패턴에서 있는 너무많은 생성자에 대한 부담이 없으며 필요한 변수만 세팅해 줄 수 있는 장점을 가집니다.

public class Person {

    private String name;

    private int age;

    private String email;

    private String phone;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}
public class Client {

    public void client() {
        Person person_1 = new Person();
        person_1.setName("사바라다");
        person_1.setAge(29);
        person_1.setEmail("koangho93@naver.com");
        person_1.setPhone("010-xxxx-xxxx");

        Person person_2 = new Person();
        person_2.setName("사바라다");
        person_2.setAge(29);
        person_2.setEmail("koangho93@naver.com");

        Person person_3 = new Person();
        person_3.setName("사바라다");
        person_3.setAge(29);
    }
}

하지만 이 패턴은 치명적인 단점을 가지고 있습니다. 바로 생성자로 초기화가 이루어지지 않는다는 것입니다. 생성자로 초기화가 이루어지지 않으며 set을 통해 값을 세팅하기 때문에 일관성을 보장할 수 없습니다. 즉, Person 객체를 만들고 이를 thread간 공유해서 사용할 때 누군가가 수정한다면 이는 이객체를 사용하는 모든 thread에 영향을 주게 됩니다. 다른 문제는 변경 불가능(immutable) 클래스를 만들 수 없는 것입니다.

빌더 패턴

점층적 계층 패턴, 자바 빈 패턴의 단점을 모두 해결할 수 있는 패턴이 있습니다. 바로 빌더 패턴입니다. 빌더 패턴은 필요한 객체를 Client가 직접 만들지 않고 빌더 객체를 이용하여 우회하는 방법입니다. 먼저 필수 인자들은 빌더 객체에 전달하고 선택적 파라미터는 체이닝으로 추가해나가는 것입니다. 아래 코드를 보도록 하겠습니다.

코드를 보시면 Person 생성자는 다른 방식으로 생성하지 못하도록 private로 정의한 후 내부 static 클래스인 Builder객체를 파라미터로 합니다. Builder 객체의 public Person build()를 봐주시기 바랍니다. 해당 메서드를 보시면 Person 객체를 생성하는 것을 알 수 있습니다. 즉, Builder는 필요한 변수를 세팅한 후 Person 객체를 생성하는 것입니다.

public class Person {

    private String name;

    private int age;

    private String email;

    private String phone;

    private Person(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.email = builder.email;
        this.phone = builder.phone;
    }

    // 빌더 객체
    public static class Builder {

        private String name;

        private int age;

        private String email;

        private String phone;

        public Builder(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public Builder email(String email) { // 이메일 세팅후 자기객체 반환
            this.email = email;
            return this;
        }

        public Builder phone(String phone) { // 휴대폰 세팅후 자기객체 반환
            this.phone = phone;
            return this;
        }

        public Person build() { // Builder 객체를 이용하여 Person 객체 생성
            return new Person(this);
        }
    }
}

아래는 빌더 패턴을 이용하여 객체를 생성하는 방법입니다. Builder 객체를 생성하면서 Builder 생성자에 필수적인 값을 세팅한 후 email과 phone은 옵셔널한 값으로 선택적으로 추가후 마지막에 build를 합니다. 이렇게 하면 마지막에는 결국 Person 객체가 생성됩니다.

public class Client {

    public void client() {
        Person person_1 = new Person.Builder("사바라다", "20")
            .email("koangho93@naver.com")
            .phone("010-xxxx-xxxx")
            .build();

        Person person_2 = new Person.Builder("사바라다", "20")
            .email("koangho93@naver.com")
            .build();

        Person person_3 = new Person.Builder("사바라다", "20").build();
    }
}

마무리

오늘은 Lombok에서 사용되는 Builder 패턴에 대해서 알아보았습니다.

Lombok의 Builder 패턴은 이펙티브 자바에서 말한 Builder 패턴을 사용하고 있습니다.

사실 이 Builder 패턴은 코드 디자인 패턴의 창시라고 할 수 있는 책 GoF에서 말하는 Builder 패턴과는 다릅니다.

GoF의 빌더 패턴과는 어떻게 다른지 다음에 다른 포스팅으로 찾아뵙도록 하겠습니다.

감사합니다.

참조

이펙티브 자바

댓글