코딩하는 털보

이펙티브 자바, 아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라. 본문

Book/이펙티브 자바

이펙티브 자바, 아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라.

이정인 2021. 8. 10. 01:05

이펙티브 자바, 아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라.

생성자든 정적 팩토리 메서드든 매개변수가 많다면 적절하게 대응하기 쉽지 않다. 생성자에 매개변수가 많을 때 사용하는 방법들을 알아보았다.

  • 점층적 생성자 패턴
    필수적 매개변수만 받는 생성자부터 시작해서 선택적 매개변수 한 개씩을 늘려가면서 모든 매개변수를 받는 생성자까지 늘려가는 단순한 방식이다.

    public class Book {
    
        private String title;
        private String author;
        private int price;
        private String publisher;
        private String description;
        private String subTitle;
    
        public Book(String title, String author, int price) {
            this(title, author, price, null, null, null);
        }
    
        public Book(String title, String author, int price, String publisher) {
            this(title, author, price, publisher, null, null);
        }
    
        public Book(String title, String author, int price, String publisher, String description) {
            this(title, author, price, publisher, description, null);
        }
    
        public Book(String title, String author, int price, String publisher, String description, String subTitle) {
            this.title = title;
            this.author = author;
            this.price = price;
            this.publisher = publisher;
            this.description = description;
            this.subTitle = subTitle;
        }
    }

    문제는 사용자가 설정하길 원하지 않는 매개변수 까지 포함할 수 있다는 것.
    예를들어 아래와 같다.
    Book myBook = new Book("이펙티브 자바 3/E", "조슈아 블로크", 36000, null, "자바를 공부하기에 적절한 책입니다.");
    description 멤버를 설정하기 위해서는 원치 않더라도 publisher에 null이나 다른 값을 입력해야 한다.

    점층적 생성자 패턴을 사용할 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어려워진다.
    특히 매개변수의 순서를 잘 알아야하는데, 같은 타입이지만 서로 다른 매개변수의 순서를 다르게 입력하더라도 컴파일러는 실수인지 알지 못한다.

  • 자바빈즈 패턴
    우리가 흔히 볼수있는 세터를 사용하는 방법이다. 자바빈즈 패턴은 점층적 생성자 패턴에 비해서 읽고 사용하기 쉬운 코드가 된다.

    public class Book {
    
        private String title;
        private String author;
        private int price;
        private String publisher;
        private String description;
        private String subTitle;
    
        public Book() {
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public void setAuthor(String author) {
            this.author = author;
        }
    
        public void setPrice(int price) {
            this.price = price;
        }
    
        public void setPublisher(String publisher) {
            this.publisher = publisher;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public void setSubTitle(String subTitle) {
            this.subTitle = subTitle;
        }
    }

    그러나 자바빈즈 패턴은 객체 하나를 만들려면 메서드를 여러개 호출해야하고 객체가 완성되기 전 까지는 일관성이 무너진 상태에 놓이게 된다는 심각한 단점이있다.

        public static void main(String[] args) {
            Book myBook = new Book();
            myBook.setTitle("이펙티브 자바 3/E");
            myBook.setAuthor("조슈아 블로크");
            myBook.setPrice(36000);
            //------- 여기까지 오기 전까지는 일관성이 무너진 상태이다.
    
            //...
        }
  • 빌더 패턴
    빌더 패턴은 점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 둘다 가져갈 수 있는 방법이다. 빌더 패턴은 파이썬과 스칼라에 있는 named optional parameter를 흉내낸 것이다.
    클라이언트는 필수 매개변수로 빌더 생성자를 호출하여 빌더 객체를 만들고 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택적 매개변수를 설정한 뒤 build() 메서드를 통해 필요한 객체를 얻을 수 있다.

    빌더는 생성할 클래스 안에 정적 멤버 클래스로 만들어두는게 보통이다. 일반적으로 빌더 생성자와 메서드에서 개별 입력 매개변수를 검사하고 build() 메서드가 호출하는 생성자에서 불변식을 검사한다.

    public class Book {
    
        private final String title;
        private final String author;
        private final int price;
        private final String publisher;
        private final String description;
        private final String subTitle;
    
        public Book(Builder builder) {
            this.title = builder.title;
            this.author = builder.author;
            this.price = builder.price;
            this.publisher = builder.publisher;
            this.description = builder.description;
            this.subTitle = builder.subTitle;
        }
    
        public static class Builder {
            private final String title;
            private final String author;
            private final int price;
    
            //선택적 매개변수는 기본값을 미리 설정한다.
            private String publisher = null;
            private String description = null;
            private String subTitle = null;
    
            public Builder(String title, String author, int price) {
                this.title = title;
                this.author = author;
                this.price = price;
            }
    
            public Builder publisher(String publisher) {
                this.publisher = publisher;
                return this;
            }
    
            public Builder description(String description) {
                this.description = description;
                return this;
            }
    
            public Builder subTitle(String subTitle) {
                this.subTitle = subTitle;
                return this;
            }
    
            public Book build() {
                return new Book(this);
            }
        }
    }

    객체를 만드는 클라이언트 코드는 아래와 같다. 쓰기 쉽고 읽기 쉽다.

    Book myBook = new Book.Builder("이펙티브 자바 3/E", "조슈아 블로크", 36000)
            //빌더의 세터메서드들이 빌더 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다. (메서드 체이닝)
            .publisher("인사이트").description("자바를 공부하기에 적절한 책입니다.").build();

    빌더 패턴은 객체를 생성하기 전에 빌더부터 생성해야되므로 성능적으로는 단점이 있다. 또한 점층적 생성자 패턴보다 코드가 장황해서 매개변수가 적을때는 적절하지 않다.

  • 핵심정리
    생성자나 정적 팩터리가 처리해야 할 매개변수가 많거나 많아질 것으로 예상된다면 빌더 패턴을 선택하는 게 더 낫다.
    매개변수 중 다수가 필수가 아니거나 같은 타입이라면 더더욱.
    빌더는 점층적 생성자 패턴보다 간결하고 자바빈즈보다 안전하다.

Comments