본문 바로가기
언어/JAVA

[JAVA] Builder 패턴

by steadyMan 2022. 6. 18.

주로 자연스럽게 자바 빈의 형태로 만들어 객체를 만들어 사용했었는데 무분별한 setter사용과 객체 일관성이 깨지는

여러 문제들로 인하여 유지보수성의 저하와 로직의 파악이 힘들어지는 문제 등을 많이 경험하였다. 

사용할 때는 편하게 사용하겠지만 무분별한 사용은 문제를 야기할 수 있기 때문에 다른 패턴들에 대해 학습한 결과를

Builder 패턴을 중심으로 정리합니다. 

객체 생성의 주요 방법 

  • 점층적 생성자 패턴
  • 자바 빈즈 패턴 
  • 빌더 패턴

점층적 생성자 패턴 

생성자에서 필수 필드값과 선택적 필드 값을 활용하여 모든 맴버 변수를 포함하도록 점층적인 여러 개의 생성자를 생성하는 패턴 

public class Body {
    // 필수 맴버변수
    private final int brain;
    private final int heart;
    // 선택적 맴버변수
    private final int eye; 
    private final int mouse;

    // 필수 맴버변수 생성자
    Body(int brain, int heart) {
    	this(brain, heart, 0, 0);
    }
    // 모든 맴버변수를 할당하는 생성자
    Body(int brain, int heart, int eye) {
        this(brain, heart, eye, 0);
    }
    
    Body(int brain, int heart, int eye, int mouse) {
    	this.brain = brain;
        this.heart = heart;
        this.eye = eye;
        this.mouse = mouse;
    }
}

점층적 생성자 패턴은 일관성, 불변성이 유지되지만 일단 코드의 양이 많고 Body클래스의 필드가 늘어나거나 줄어들면

그 만큼 작업량 도 많아지고 불필요한 할당도 해야 하며 인스턴스 생성 시에 가독성도 떨어진다.

자바빈 패턴(JavaBean)

setter을 활용하여 데이터 할당을 하는 패턴 

가장 흔하게 볼 수 있는 패턴 

public class Body {
    private int brain;
    private int heart;
    private int eye; 
    private int mouse;

    public setBrain(int brain) {
        this.brain = brain;
    }

    public setHeart(int heart) {
        this.heart = heart;
    }

    public setEye(int eye) {
        this.eye = eye;
    }

    public setMouse(int mouse) {
        this.mouse = mouse;
    }	
}
class Main {
	public static void main(String[] args) {
    	Body body = new Body();
        body.setHeart(1)
        body.setBrain(1)
	}
}

코드의 양이 상대적으로  적고 가독성도 개선됐다. setter메서드를 활용하면 내가 할당하는 데이터가 어떤 필드의 데이터인지 역할을 파악하고 할당할 수 있다. 하지만 변경이 불가한 인스턴스를 만들 수 없다. 작업자가 언제든지  setter로 필드 값을 변경할 수 있기 때문이다. 이렇기에 일관성, 불변 원칙을 지킬 수 없는 패턴이다. 

빌더 패턴 

코드의 가독성이 높고 객체의 일관성과 불변의원칙을 지킬 수 있다. 

메서드를 통해 필드데이터를 할당하여 어떤데이터에 대한 할당인지 파악이 가능하다. 

점층 생성자 패턴의 안정성 과 빈즈 패턴의 가독성이 합쳐진 패턴 

public class Body {
    // 필수 맴버변수
    private final int brain;
    private final int heart;
    // 선택적 맴버변수
    private int eye;
    private int mouse;

    public static class Builder {
        private final int brain;
        private final int heart;
        private int eye;
        private int mouse;
        // Builder 생성자 final 필드 초기화 
        public Builder(int brain, int heart) {
            this.brain = brain;
            this.heart = heart;
        }

        public Builder Eye(int eye) {
            this.eye = eye;
            return this;
        }

        public Builder Mouse(int mouse) {
            this.mouse = mouse;
            return this;
        }
        // Body의 인스턴스 리턴
        public Body build() {
            return new Body(this);
        }
    }
    // 생성자의 접근제한자를 private로 설정하여 생성을 제한한다.
    private Body(Builder builder) {
        this.brain = builder.brain;
        this.heart = builder.heart;
        this.eye = builder.eye;
        this.mouse = builder.mouse;
    }
}

내부에 static class을 만들고 필수 필드 값은 생성자로 입력받고 선택적 데이터는 메서드 체인을 통해 입력받는다. 

setter와 마찬가지로 필드이름의 메서드로 데이터를 할당하기 때문에 무슨 값을 의미하는지 파악하기 쉽고 생성자와 다르게

할당순서에 영향이 없다. 

 

하지만 작성해야 하는 코드의 양이 많기 때문에 빌드 패턴은 선택적 인자를 받는 생성자가 있고, 필드의 개수가 많아져 인스턴스 생성 시 파악이 힘들어지는 상황에 사용해야 한다. 

 

Lombok라이브러리의 @Builder 애노테이션을 사용하면 코드를 직접구현하지 않아도 편리하게 빌더 패턴을 사용할 수 있다. 

 

모든 상황에 맞는 패턴은 없지만 다양한 패턴을 숙지하고 상황에 맞을때 사용할 수 있도록 하자.

 

댓글