It's Ward

[Spring] 빌더 패턴(Bulider Pattern)에 대해 알아보자 본문

Java/Spring

[Spring] 빌더 패턴(Bulider Pattern)에 대해 알아보자

I'm ward 2022. 6. 21. 02:50

빌더 패턴은 GoF 디자인 패턴 중 생성 패턴에 해당하고 주로 복잡한 단계를 거쳐야 생성되는 개체의 구현을 서브 클래스에게 넘겨줄 때 많이 사용된다. 

빌더의 생성자나 메소드에서 유효성 확인을 할 수 있고, 많은 매개변수들을 혼합해서 확인해야 하는경우, build 메소드에서 호출하는 생성자에서 확인할 수 있다.

또, 실패하면 예외를 발생시켜 어떤 매개변수가 잘못되었는지 확인도 할 수 있는데, 이러한 빌더 패턴에대해서 자세하게 알아보도록 한다.

1. 왜 빌더 패턴(Bulider Pattern)을 사용해야 하는가?

빌더 패턴은 다음과 같은 장점을 가지고 있다.

1) 개발자가 필요한 데이터만 설정할 수 있음. 
2) 가독성을 높이고, 유연한 변경이 가능함

1) 필요한 데이터만 설정할 수 있음

예시를 들어보자. 프로그램을 요청한 클라이언트가 멤버가 몇번 들어왔는지 체크를 하고싶은데 아직 윗선의 결제가 나지않아 지금 만들어야할지, 넣어야할지 아니면 다른 필드값이 추가되어야할 지 모른다는 가정이다.

빌더 패턴을 모르고 만들었을 경우 다음과 같이 작성할 것이다.

1. 우선 다 만들어 더미값을 만들어 넣어주는 방법
2. 생성자를 또 만들어 만들어 추가하는 방법
3. 정적 메소드를 이용해 추가하는 방법

//더미값을 이용해 넣어주는 방법
Member member = new Member( "ward", "", 0, "VIP");

public class Member {

    private String name;
    private String password;
    private int count;
    private String grade;
    
    //생성자를 만들어 추가하는 방법
    public Member (String name, String password, String grade){ 
    	
        this.name = name;
        this.password = password;
        this.grade = grade;
        
    }
    
    //정적 메소드를 이용해 추가하는 방법
    public static User newMember(String name, String password, String grade){
    	return new Member(name, password, 0 , VIP);
    }
    
}

이런 작업이 별로 없다면 당연히.. 해줄 수 있겟지만 굉장히 불필요한 작업이고, 요구사항이 변할때마다 유지보수 시간은 늘어만 날것이다.

하지만 빌더를 이용한다면, 편하게 사용할 수 있다. (사용하지 않는 값에 null / 0 / false 값을 일일히 주지 않아도 괜찮다.)

Member member = Member.builder()
		    .name("ward")
                    .password("")
                    .grade("VIP")
                    .build();

 

2) 가독성을 높이고, 유효성 검증을 할 수 있음.

또다시 예시를 들어보자. Member 클래스에 새로운 나이(age)와, 사용한 금액(countMoney)을 추가해야한다고 하면 다음과 같이 생성자로 인스턴스화 해야할 것이다.

//이전 
Member member = new Member( "ward", "", 0, "VIP");

//변경
Member member = new Member( "ward", "", 0, "VIP", 29, 1040510);

갑작스렇게 많아진 매개변수에, 어떤 값을 써야하는지 모르게되고 결국, 추가될수록 코드를 보기 점점 힘들어질 것이다. 최악의 경우 매개변수를 잘못 입력하여 오류가 발생 할 수도 있다.

Member member = Member.builder()
		    .name("ward")
                    .grade("VIP")
                    .password("")
                    .countMoney(1040510)
		    .age(29)
                    .build();

위와 같은 코드를 보면 어떤 데이터가 Member에 추가되어야하는지, 명시적으로 표현하고 있어 직관적으로 보기 쉽고 또한 순서가 변경되어도 잘 작동된다.

 

유연한 변경은, 새롭게 추가되는 변수때문에 생성자로 만들었을 경우, 기존의 코드를 모두 수정해야하는데 이 또한 변경없이 사용할 수 있다는 장점이 있다.

@Test
public void 테스트1(){
//이전 
//Member member = new Member( "ward", "", 0, "VIP");

//변경
Member member = new Member( "ward", "", 0, "VIP", 29, 1040510);

}

...

public void 테스트99(){
//이전 
//Member member = new Member( "ward", "", 0, "VIP");

//변경
Member member = new Member( "ward", "", 0, "VIP", 29, 1040510);

}

또한, 동적으로 빌더를 통해 잘못된 데이터가 들어간 경우(검증에 실패한경우)  illegalargumentexception 을 통해 에러메세지로 어떠한 매개변수가 잘못되었는 지 확인할 수 있다.

 

빌더 패턴을 더 잘 사용하려면..

 

1. 불필요한 @Setter / @Data 의 사용은 NO!

변경 가능성을 최소화 하는 경우에는 Setter를 구현하지 않음으로서 final로 선언한 것과 같은 불변성을 확보할 수 있다. (의존관계의 변경이 필요한 상황이 없는경우, 변경의 가능성을 배제하고 불변성을 보장하자)

2. Class 레벨이 아닌 생성자 레벨에 적용하자

@Builder를 Class에 적용하면, 모든 맴버 필드에 대해 매개변수를 만드는 기본 생성자를 만들기때문에 만약 어떤 변수가 데이터베이스 PK 생성전략에 의존한다면, 그 데이터는 변경되어서는 안되기때문이다. Class 레벨에서는 제한하기가 어렵다.

public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private String password;
    private int count;
    private String grade;
    
    @Builder
    public Member (String name, String password, int count, String grade){ 
    	
    this.name = name;
    this.password = password;
    this.count = count;
    this.grade = grade;
        
   }
}

다음과 같이 Builder 생성시 파라미터가 없는 경우 객체 생성시 id값을 넘겨받지 못하게 제한할 수 있습니다. 

Member member = Member.builder()
		    .name("ward")
                    .password("")
                    .grade("VIP")
                    .id(2L) // 컴파일 시 에러 발생
                    .build();

 

3. 초기값이 필요할 때에는 Builder.Default를 이용하자

Builder는 Wrapper 타입을 사용하여 값을 설정하지 않으면 자동으로 다음과 같은 값을 가진다.

Wrapper / Object : null
int : 0
boolean : false

만약에 기본값을 설정하고싶다면, @Builder.Default를 이용하여 기본값을 설정해 줄 수 있는데, 다음과 같이 설정하면, 기본값이 변경된다.

    @Builder.Default
    private String grade = "VIP";

 

마치며.. 

 단순하게 빌드 패턴을 사용만 할줄 알았지, 스프링 스터디를 진행하면서 궁금한 점을 하나씩 발표하기로 하였는데 스터디원님께서 좋은 기회를 만들어 주셔서 조금 더 스프링과 친해지고 관련된 디자인 패턴이라던지, 디버깅 방법이라던지 좋은 정보들을 학습할 수 있는 시간이였다고 생각한다. 

끝으로, 빌더를 이용한 테스트 방법에대해 소개한다.

https://cheese10yun.github.io/spring-builder-pattern/

 

Builder 기반으로 객체를 안전하게 생성하는 방법 - Yun Blog | 기술 블로그

Builder 기반으로 객체를 안전하게 생성하는 방법 - Yun Blog | 기술 블로그

cheese10yun.github.io

참고 자료 :

https://www.youtube.com/watch?v=OwkXMxCqWHM 

https://mangkyu.tistory.com/163

https://mangkyu.tistory.com/125

https://charliezip.tistory.com/m/17

https://dev-youngjun.tistory.com/197?category=937057 

Comments