Java

[Java] Builder 생성자 패턴

림 림 2021. 9. 10. 13:52
반응형

객체를 생성하기 위해서는 public 생성자 또는 static 팩토리 메서드를 사용할 수 있습니다. 그런데 필수 매개변수가 아닌 선택 매개변수의 개수가 많을 경우, 코드의 효율성, 안정성, 가독성을 위해 Builder 패턴을 고려하는 것이 좋습니다. 여러 생성자 패턴과 그 장단점을 알아보겠습니다.

 

Index

1. 예제 클래스

2. Telescoping Constructor Pattern(점층적 생성자 패턴)

3. JavaBeans Pattern(자바빈즈 패턴)

4. Builder Pattern(빌더 패턴)

 

 

1. 예제 클래스

다음과 같이 2개의 필수 멤버 필드와 4개의 선택 멤버 필드를 가지는 Book 클래스가 있다고 합시다.

import java.time.LocalDate;

public class Book {
    // 필수 멤버 필드
    private String title;
    private String author;

    // 선택 멤버 필드
    private String color;
    private int pages;
    private LocalDate publicationDate;
    private String weight;
}

이 Book 클래스의 생성자를 작성하는 3가지 방법에 대해 살펴보겠습니다.

 

 

2. Telescoping Constructor Pattern(점층적 생성자 패턴)

가장 단순하게는 필수 매개변수 2개를 받는 생성자부터 시작해서 3개를 받는 생성자, 4개를 받는 생성자, ••• 총 6개를 받는 생성자까지 모두 작성할 수 있습니다. 점층적 생성자 패턴은 이렇게 필요할 수 있는 모든 매개변수 조합의 생성자를 작성하는 방법입니다.

생성자 코드

import java.time.LocalDate;

public class Book {
    // 필수 멤버 필드
    private String title;
    private String author;

    // 선택 멤버 필드
    private String color;
    private int pages;
    private LocalDate publicationDate;
    private String weight;
    
    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public Book(String title, String author, String color) {
        this.title = title;
        this.author = author;
        this.color = color;
    }

    public Book(String title, String author, String color, int pages) {
        this.title = title;
        this.author = author;
        this.color = color;
        this.pages = pages;
    }

    public Book(String title, String author, String color, int pages, LocalDate publicationDate) {
        this.title = title;
        this.author = author;
        this.color = color;
        this.pages = pages;
        this.publicationDate = publicationDate;
    }

    public Book(String title, String author, String color, int pages, LocalDate publicationDate, String weight) {
        this.title = title;
        this.author = author;
        this.color = color;
        this.pages = pages;
        this.publicationDate = publicationDate;
        this.weight = weight;
    }
}

점층적 생성자 패턴으로 작성된 생성자를 이용해서 객체를 생성하려면, 원하는 매개변수를 포함한 생성자를 선택하여 사용하면 됩니다.

 

장점

객체가 불완전한 상태에 있는 것을 방지할 수 있습니다.

여기서 말하는 불완전한 상태란, 객체의 필수 멤버 필드의 값이 없는 상태를 말합니다. 생성할 때부터 필수 멤버 필드에 값을 초기화해주기 때문에, 객체가 항상 일관성을 유지할 수 있습니다.

 

단점

클라이언트 코드의 가독성이 떨어집니다.

매개변수가 많아지면 많아질 수록, 코드를 읽을 때 각 매개변수의 순서와 개수를 주의해서 보아야 그 의미를 파악할 수 있습니다. 또, 타입이 같은 매개변수가 연달아 있을 경우, 컴파일 단계에서도 알아차릴 수 없는 버그가 발생할 수 있습니다.

 

 

3. JavaBeans Pattern(자바빈즈 패턴)

자바빈즈 패턴은 매개변수가 없는 생성자로 객체를 만든 후, setter 메서드로 멤버 필드의 값을 설정하는 방법입니다.

생성자 코드

import java.time.LocalDate;

public class Book {
    // 필수 멤버 필드
    private String title;
    private String author;

    // 선택 멤버 필드
    private String color;
    private int pages;
    private LocalDate publicationDate;
    private String weight;

    public void setTitle(String title) {
        this.title = title;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void setPages(int pages) {
        this.pages = pages;
    }

    public void setPublicationDate(LocalDate publicationDate) {
        this.publicationDate = publicationDate;
    }

    public void setWeight(String weight) {
        this.weight = weight;
    }
}

위의 코드에서는 생성자가 없습니다. 생성자를 작성하지 않으면 Java에서 자동으로 기본 생성자를 만들어 줍니다. 자바빈즈 패턴으로 작성된 생성자로 객체를 만들기 위해서는 다음과 같이 기본 생성자로 멤버 필드가 비어있는 객체를 생성한 후에 setter 메소드로 값을 채워줘야 합니다.

클라이언트 코드

Book book = new Book();
book.setTitle("My Book");
book.setAuthor("me");
book.setColor("black");
book.setPages(100);
book.setPublicationDate(LocalDate.now());
book.setWeight("3kg");

 

 

장점

클라이언트 코드의 가독성이 좋습니다.

setter 메서드의 이름에 멤버 필드의 이름이 포함되어 있기 때문에, 어떤 멤버 필드에 어떤 값이 들어가는지 쉽게 파악할 수 있습니다.

 

단점

객체의 필수 멤버 필드에 값이 들어가기 전까지는 객체가 불완전한 상태에 놓입니다.

기본 생성자로 객체를 생성한 직후에는 필수 멤버 필드에 빈 값이 들어가게 됩니다. 

객체 하나를 만들기 위해 여러 메서드가 호출됩니다.

생성자 하나만 호출하면 되는 점층적 패턴과 달리, 자바빈즈 패턴은 여러 setter 메서드를 호출합니다. 매개변수가 많아질 수록 더 많은 메서드를 호출해야 합니다.

 

 

4. Builder Pattern(빌더 패턴)

빌더 패턴은 점층적 생성자 패턴의 안정성과 자바빈즈 패턴의 가독성을 겸비한 패턴입니다. 빌더 패턴을 사용하는 일반적인 순서는 다음과 같습니다.

1. 클래스 안에 static 멤버 클래스로 Builder 클래스를 만들어 줍니다.

2. Builder 클래스는 생성할 클래스의 멤버 필드를 똑같이 가집니다.

3. Builder 클래스의 선택 멤버 필드를 기본값으로 초기화합니다.

4. Builder 클래스에 필수 멤버 필드를 매개변수로 하는 생성자를 선언합니다.

5. 선택 매개변수의 값을 저장하고 Builder 객체를 반환하는 메서드를 만듭니다.

6. 생성할 클래스의 객체를 반환해주는 builder 메서드를 만듭니다.

7. 생성할 클래스의 생성자로 Builder 객체를 받는 생성자를 만듭니다.

생성자 코드

import java.time.LocalDate;

public class Book {
    // 필수 멤버 필드
    private String title;
    private String author;

    // 선택 멤버 필드
    private String color;
    private int pages;
    private LocalDate publicationDate;
    private String weight;

    // 1. 클래스 안에 static 멤버 클래스로 Builder 클래스를 만들어 줍니다.
    public static class Builder {
        // 2. Builder 클래스는 생성할 클래스의 멤버 필드를 똑같이 가집니다.
        private String title;
        private String author;

        // 3. Builder 클래스의 선택 멤버 필드를 기본값으로 초기화합니다.
        private String color = "white";
        private int pages = 0;
        private LocalDate publicationDate = LocalDate.now();
        private String weight = "0kg";

        // 4. Builder 클래스에 필수 멤버 필드를 매개변수로 하는 생성자를 선언합니다.
        public Builder(String title, String author) {
            this.title = title;
            this.author = author;
        }

        // 5. 선택 매개변수의 값을 저장하고 Builder 객체를 반환하는 메서드를 만듭니다.
        public Builder color(String color) {
            this.color = color;

            return this;
        }

        public Builder pages(int pages) {
            this.pages = pages;

            return this;
        }

        public Builder publicationDate(LocalDate publicationDate) {
            this.publicationDate = publicationDate;

            return this;
        }

        public Builder weight(String weight) {
            this.weight = weight;

            return this;
        }

        // 6. 생성할 클래스의 객체를 반환해주는 builder 메서드를 만듭니다.
        public Book build() {
            return new Book(this);
        }
    }

    // 7. 생성할 클래스의 생성자로 Builder 객체를 받는 생성자를 만듭니다.
    private Book(Builder builder) {
        title = builder.title;
        author = builder.author;
        color = builder.color;
        pages = builder.pages;
        publicationDate = builder.publicationDate;
        weight = builder.weight;
    }
}

클라이언트는 필요한 객체를 생성자로 직접  만들지 않습니다. 일반적으로 다음과 같은 방법으로 객체를 생성합니다.

1. 필수 매개변수로 이루어진 생성자를 통해 Builder 객체를 얻습니다. 

2. Builder 객체가 제공하는 메소드로 선택 멤버 필드의 값을 저장합니다.

3. 매개변수가 없는 build 메소드로 생성하고자 하는 클래스의 객체를 얻습니다.

클라이언트 코드

Book b = new Book.Builder("title", "author")
  .color("black")
  .pages(100)
  .publicationDate(LocalDate.now())
  .weight("2kg")
  .build();

 

장점

점층적 패턴의 안정성과 자바빈즈 패턴의 가독성을 모두 가집니다.

Builder 생성자를 통해 객체가 불완전한 상태에 있는 것을 방지해줍니다. Builder 멤버 메서드의 이름에 멤버 필드의 이름이 포함되어 있어서 가독성이 좋습니다.

 

반응형