2장 객체 생성과 파괴 - 빌더

정지수 JisooJung·2021년 11월 20일
0

Effective Java 스터디

목록 보기
3/6

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

점층적 생성자 패턴(telescoping constructor pattern)

필수항목/선택항목으로 이루어진 클래스에서, 선택항목 개수+1 만큼의 생성자를 정의하는 것.
ex) 필수 항목 2개 (int a, b) 선택 항목 3개(int c, d, e)로 구성된 클래스 Words

public class Words {
    
    private final int a;    // 필수
    private final int b;    // 필수
    private int c;          // 선택
    private int d;          // 선택
    private int e;          // 선택

    // 필수항목
    public Words(int a, int b) {
    	this(a, b, 0);
    }

    // 필수항목 + 선택항목 c
    public Words(int a, int b, int c) {
    	this(a, b, c, 0);
    }
    
    // 필수항목 + 선택항목 c, d
    public Words(int a, int b, int c, int d) {
    	this(a, b, c, d, 0);
    }
    
    // 필수항목 + 선택항목 c, d, e
    public Words(int a, int b, int c, int d, int e) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
        this.e = e;
    }
}

필드의 개수만큼 생성자의 개수, 파라미터의 개수가 늘어남

  • 클라이언트(1장 참고) 코드를 작성하거나 읽기 어려워짐. ex) 각각의 파라미터가 어떤 필드를 위한 값인지 파악 어려움
  • 매개변수의 순서가 바뀌었을 때 컴파일러가 감지할 수 없음 > 런타임 에러 야기

자바 빈즈 패턴(JavaBeans pattern)

매개변수가 없는 생성자로 객체를 만든 후, 세터(setter) 메서드로 필드 값 지정
ex) 클래스 예제

public class Words {
    
    private int a = -1;    // 필수
    private int b = -1;    // 필수
    private int c = 0;     // 선택
    private int d = 0;     // 선택
    private int e = 0;     // 선택

    public Words() {}

    // 세터 메서드
    public void setA(int val) {a = val;}
    public void setB(int val) {b = val;}
    public void setC(int val) {c = val;}
    public void setD(int val) {d = val;}
    public void setE(int val) {e = val;}
}

ex) 호출 예제

Words word = new Words();
word.setA(1);
word.setB(2);
word.setC(3);
word.setD(4);
word.setE(5);

가독성은 개선되었으나, 단점 존재

  • 객체 하나 생성 시 다수의 메서드 호출 필요
  • 객체가 완전히 생성되기 전까지 일관성(consistency) 무너짐
  • 클래스를 불변으로 만들 수 없음

개선방안: freeze - 객체가 완전히 생성되기 전까지 사용할 수 없도록 하는 메서드.
그러나, 컴파일러가 freeze가 제대로 호출되었는지 확인할 수 없으므로 런타임 오류에 취약

빌더 패턴(Builder pattern)

사용방법 )

  1. 필수 매개변수로 생성자 호출해 빌더 객체 생성
  2. 빌더 객체의 세터 메서드로 선택 매개변수 설정
  3. build 메서드를 호출하여 최종 객체 반환

ex) 클래스 예제

public class Words {
    
    private final int a;    // 필수
    private final int b;    // 필수
    private final int c;    // 선택
    private final int d;    // 선택
    private final int e;    // 선택

    // 빌더 클래스
    public static class Builder {
        // 필수 매개변수
        private final int a;
        private final int b;

        //선택 매개변수
        private int c = 0;
        private int d = 0;
        private int e = 0;

        public Builder(int a, int b) {
            this.a = a;
            this.b = b;
        }

        public Builder c (int val) {
            c = val; return this;
        }

        public Builder d (int val) {
            d = val; return this;
        }

        public Builder e (int val) {
            e = val; return this;
        }

	// 빌드 메서드
        public Words build() {
            return new Words(this);
        }
    }

    private Words(Builder builder) {
        a = builder.a;
        b = builder.b;
        c = builder.c;
        d = builder.d;
        e = builder.e;
    }
}

ex) 호출 예제

Words word = new Words.Builder(1, 2)
                            .c(3)
                            .d(4)
                            .e(5)
                            .build();

단점)

  • 객체 생성 시 빌더부터 만들어야 함 -> 성능에 문제가 될 수 있음
  • 코드가 다소 장황함 -> Lombok 의 Builder 애너테이션을 통해 보완 가능

장점)

  • 점층적 생성자 패턴의 안정성 + 자바 빈즈 패턴의 가독성을 겸비
  • 계층적 빌더 사용 시, 클라이언트가 형변환을 신경쓰지 않고도 사용 가능 (공변 반환 타이핑 covariant return typing)
  • 상당히 유연

처리할 매개변수가 4개 이상인 경우 빌더 패턴을 쓰는 것이 좋다!


Reference

조슈아 블로크Joshua Bloch, 『이펙티브 자바 Effective Java 3/E』, 개앞맵시(이복연) 옮김, 인사이트(2018), p14-22.

profile
Study&Work&Log

0개의 댓글