[Java] Builder Pattern 왜 쓰는 거야?

devdo·2021년 5월 10일
1

Java

목록 보기
14/59
post-thumbnail

빌더 패턴(Builder Pattern)에 대해서 알아보자.

점증적 생성자패턴 vs 빌더 패턴

예전에는 점증적 생성자패턴 또는 자바 빈(Bean)패턴 으로 객체 인자들을 만들어야 했다.
참고 : https://dev-youngjun.tistory.com/197

이들의 문제점을 보완하여 만든 것이 빌더패턴이다. 또 Lombok의 @Builder 어노테이션을 이용하면 훨씬 더 간편하게 사용할 수 있다.


빌더패턴을 적용 시의 효과

빌더패턴을 적용 시에는 다음의 효과를 기대할 수 있다.

1) new는 해롭다, new 연산자를 사용하기 위해서는 구체 클래스에 의존할 수밖에 없어 결합도가 높아진다. 오브젝트 271p
2) new를 안쓰니 불필요한 생성자, 생성자 인자들을 클라이언트 객체에서 작성 안해도 됨
3) 명시적 선언으로 이해하기 쉬움.
4) 인자 순서가 틀려도 오류가 아님.

만약 요건이 자주 변경되어서 매번 생성자를 만드는 일이 생긴다면?

이런 상황을 좀더 직관적인 객체로 만들어서 손이 덜가게 하는 것이 바로 빌더 패턴 이다.


빌더패턴 어노테이션

@Builder 어노테이션은 빌드 클래스를 자동으로 만들어주는데,
이 과정에서 디폴트 생성자 대신 모든 필드값을 가지는 생성자만 생성해버린다고 한다.
그래서 @AllArgsConstructor도 같이 써줘야 한다.

ex)

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class UserInfoLombok {
    private String name;
    private int age;
    private String addr;
}

다음과 같이 빌더 패턴으로 더 깔끔하게 쓸 수 있다.

UserInfoLombok userInfoLombok = UserInfoLombok.builder()
        .name("Lombok 적용")
        .addr("주소 테스트")
        .build();

System.out.println(userInfoLombok);

// 결과=> UserInfoLombok(name=Lombok 적용, age=0, addr=주소 테스트)

💥 빌더 패턴 @NoArgsConstrutor과 @AllArgsConstrutor의 문제

단, 문제가 있다. @NoArgsConstrutor 어노테이션을 @Builder 어노테이션과 함께 쓰면 오류가 발생한다!

이 오류 문제에 있어서 해결법이 보통 @AllArgsConstrutor를 쓰는 것인데 @AllArgsConstrutor 자체는 상당히 위험한다!

@AllArgsConstrutor는 클래스에 존재하는 모든 필드에 대한 생성자를 자동으로 생성하는데, 선언된 필드의 순서대로 생성자를 생성하기 때문에

인스턴스 객체 생성시 변수의 순서를 바꾸면 생성자의 입력 값 순서도 바뀌게 되어
검출되지 않는 치명적인 오류를 발생시킬 수 있기 때문이다!

그래서 @AllArgsConstrutor을 쓰지 않고

@NoArgsConstrutor과 함께
@Builder어노테이션을 클래스에 놓지 않고 생성자에 놓는 것으로 권장한다.

참고) https://velog.io/@maketheworldwise/Builder-AllNoArgsConstructor-제대로-알고-사용하자


해결된 Builder 클래스 형태

@Data
// 기본 생성자의 생성을 방지, 지정한 생성자를 사용하도록 강제! 
// PROTECTED: 상속은 가능
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

	private String name; 
	private String email;
	private String picture;
	private Role role;

// 생성자에 @Builder 적용
    @Builder
    public Member(String name, String email, String picture, Role role) {
        this.name = name;
        this.email = email;
    }
    
}

자세한 내용은 다음 블로그를 참고하길 바란다.


Dto 사용법

✅ RequestDto 에서는 Builder 패턴을 사용하지 않는다. 사용자 요청에 받기만 하면 되고 모든 요청에 관한 필드를 처음 셋팅하는 초기화를 하는 final을 쓸 수가 없다.

단, ResponseDto는 다르다. Entity를 빌더패턴으로 감싸서 응답값으로 보내야 하기에 비즈니스로직외 변하지 않게 @Setter를 쓰지 말아야 하고 final 필드도 같이 써준다.

1) RequestDto: @RequestBody, @ModelAttribute(디폴트: 생략가능) 쓰는 Dto

-> @Getter, @Setter, @ToString, final x

    @Getter
    @Setter
    @ToString
    public class RegisterItemOptionGroupRequest {
        private Integer ordering;
        private String itemOptionGroupName;
        private List<RegisterItemOptionRequest> itemOptionList;
    }

2) ResponseDto

-> @Builder + final , @Setter x

   // Dto에 굳이 생성자에다 @Builder 패턴을 넣지 않고 클래스에 위치시켜준다.
    @Getter
    @Builder
    @ToString
    public class TestDto {
        private final String itemToken;
        private final Long partnerId;
        private final String itemName;
        private final Long itemPrice;
        private final Item.Status status;
        private final List<ItemOptionGroupInfo> itemOptionGroupList;
    }


참고

profile
배운 것을 기록합니다.

0개의 댓글