이펙티브 자바

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

climb-up 2023. 11. 6. 21:23


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


필수 매개변수만 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자 등등 경우의 수만큼 받는 생성자를 늘려가는 방식이다.

[점층적 생성자 패턴 예제]

//점층적 생성자 패턴
public class reservation{
    private final int reseDate;         //날짜(필수)
    private final String reseProgramNm; //예약프로그램명(필수)
    private final String reseNm;        //예약자명(필수)
    private final String reseWithNm;    //동행자(선택)
    private final String coupon;        //쿠폰사용(선택)
    
    public reservation(int reseDate, String reseProgramNm, String reseNm){
        this(reseDate, reseProgramNm, reseNm, “0”);
    }
    public reservation(int reseDate, String reseProgramNm, String reseNm, String reseWithNm){
        this(reseDate, reseProgramNm, reseNm, reseWithNm, “0”);
    }
    
    public reservation(int reseDate, String reseProgramNm, String reseNm, String reseWithNm, String coupon){
        this.reseDate 			= reseDate;
        this.reseProgramNm   = reseProgramNm;
        this.reseNm 			= reseNm;
        this.reseWithNm 		= reseWithNm;
        this.coupon 			= coupon;
    }
    
    public static void main(String[] args){
    	reservation rese = new reservation(20231107, “피부테라피”, “한지민”, “김이나”, “Y”);
    	System.out.println(rese);
    }
}


사용할 매개변수를 모두 포함한 생성자를 골라서 사용하면된다. 하지만 위에 예제는 매개변수가 적지만 많은 매개변수를 사용하는 프로그램에서는 수가 많아지면 걷잡을 수 없기 때문에 좋은 방법은 아니다.
클라이언트가 실수로 매개변수의 순서를 바꿔 건네줘도 컴파일러는 알아채지 못하고, 런타임에 엉뚱한 동작을 하게된다.



[자바빈즈 패턴(JavaBeans pattern)]


매개변수가 없는 생성자로 객체를 만든 후, 세터 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식이다.

[자바빈스 패턴 예제]

//자바빈즈 패턴
public class reservation {
    private int reseDate = 0;          //날짜(필수)
    private String reseProgramNm = ""; //예약프로그램명(필수)
    private String reseNm = "";        //예약자명(필수)
    private String reseWithNm = "";    //동행자(선택)
    private String coupon = "N";       //쿠폰사용(선택)
    
    public reservation() {}
    
    //세터 메서드들
    public void setReseDate(int val)         {reseDate = val;}
    public void setReseProgramNm(String val) {reseProgramNm = val;}
    public void setReseNm(String val)        {reseNm = val;}
    public void setReseWithNm(String val)    {reseWithNm = val;}
    public void setCoupon(String val)        {coupon = val;}
    
    public static void main(String[] args){
        reservation rese = new reservation();
        rese.setReseDate(20231107);
        rese.setReseProgramNm("피부테라피");
        rese.setReseNm("한지민");
        rese.setReseWithNm("김이나");
        rese.setCoupon("Y");
        System.out.println(rese);
    }
}


단점은 객체 하나를 만들려면 메서드를 여러개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성(consistency)이 무너진 상태에 놓이게 된다.
일관성이 무너지면 클래스를 불변으로 만들 수 없고, 스레드 안전성을 얻으려면 프로그래머가 추가 작업을 해야한다.



[빌더 패턴(Builder pattern)]


점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비하고있다.필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻고, 제공하는 세터 메서드를 원하는 선택 매개변수들을 설정한 후 마지막 매개변수없는 build 메서드를 호출하여 객채를 얻는다.

[빌더 패턴 예제]

//빌더 패턴
public class reservation {
    private final int reseDate;           //날짜(필수)
    private final String reseProgramNm;   //예약프로그램(필수)
    private final String reseNm;          //예약자명(필수)
    private final String reseWithNm;      //동행자(선택)
    private final String coupon;          //쿠폰사용(선택)
    
    public static class Builder{
        //필수 매개변수
        private final int reseDate;         //날짜(필수)
        private final String reseProgramNm; //예약프로그램(필수)
        private final String reseNm;        //예약자명(필수)
        //선택 매개변수 - 기본값으로 초기화한다.
        private String reseWithNm = "";     //동행자(선택)
        private String coupon     = "N";    //쿠폰사용유무(선택)
        
        public Builder(int reseDate, String reseProgramNm, String reseNm) {
            this.reseDate = reseDate;
            this.reseProgramNm = reseProgramNm;
            this.reseNm = reseNm;
        }
        public Builder reseWithNm(String val) {
            reseWithNm = val;
            return this;
        }
        public Builder coupon(String val) {
            coupon = val;
            return this;
        }
        public reservation build() {
            return new reservation(this);
        }
    }
    private reservation(Builder builder) {
        reseDate = builder.reseDate;
        reseProgramNm = builder.reseProgramNm;
        reseNm = builder.reseNm;
        reseWithNm = builder.reseWithNm;
        coupon = builder.coupon;
    }
    
    public static void main(String[] args) {
        reservation rese = new reservation.Builder(20231107, "피부테라피", "한지민")
        .reseWithNm("김이나").coupon("Y").build();
        System.out.println(rese);
    }
}

예약클래스는 빌더의 세터 메서드들의 빌더 자신을 반환하기 때문에 연쇄적으로 흐른다는 뜻으로 메서드 연쇄(method chaining)라 한다.
빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기 좋다. 빌더를 사용하면 가변인수(varargs) 매개변수를 여러개 사용할 수 있다. 빌더 패턴은 상당히 우연하고, 하나로 여러 객체을 순회하면서 만들 수 있다. 객체마다 부여되는 일련번호와 같은 특정 필드는 빌더가 알아서 채우도록 할 수도 있다.

단점은 빌더부터 만들어야하고, 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수 있다. 생성자나 정적 펙털 방식으로 시작했다가 나중에 매개변수가 많아져 빌더 패턴으로 전환할 수도 있다. 이렇게 전환되는 상황보단 애초에 빌더로 시작하는 편이 나을 때가 많다.