📕 빌더 패턴
📗 빌더 패턴이란?
- 복잡한 객체들을 단계별로 생성할 수 있도록 하는 GoF 디자인 패턴 중 생성 패턴에 해당하는 패턴이다.
- 객체를 생성하는 클래스와 표현하는 클래스를 분리하여, 동일한 절차에서도 서로 다른 표현을 생성하는 방법을 제공한다.
📗 왜 나왔을 까?
객체를 생성시 생성자(Constructor)만 사용할 때 발생할 수 있는 문제를 개선하기 위해 나왔다.
예시로 다음과 같은 Member 클래스가 있다고 하자.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Member extends BaseTimeEntity {
/**
* 회원 목록 테이블
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private long id;
@Column(unique = true, nullable = false, length = 1000)
private String apiKey;
@Column(unique = true)
private String username;
private String password;
@Enumerated(EnumType.STRING)
private Role role;
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<Character> characters = new ArrayList<>();
/**
* 연관 관계 메서드
*/
// 회원가입 생성자
public Member(MemberSignupDto signupDto) {
this.apiKey = signupDto.getApiKey();
this.username = signupDto.getUsername();
this.password = signupDto.getPassword();
this.role = Role.USER;
}
// user 엔티티에 character 리스트 저장
public Character addCharacter(Character character) {
characters.add(character);
character.setMember(this);
return character;
}
}
[회원가입 메소드]
public Member signup(MemberSignupDto signupDto) {
return memberRepository.save(new Member(signupDto));
}
Spring Boot와 Lombok을 쓰고 있어 Getter, Setter등의 코드는 따로 존재하지 않고,
간단한 토이 프로젝트에서는 사용할 수 있지만, 대형 프로젝트에서는 사용하면 여러 이슈가 발생한다.
- 다른 매개변수를 받게되면 그에 따른 생성자를 만들어야한다.
- 필요 없는 파라미터들이 생기면 일일이 NULL값을 넘겨줘야한다.
또한 만약 여기서 여러 매개변수를 받는 생성자를 그냥 만들면 어떻게 될까?
public Member(long id, String apiKey, String username, String password, Role role, List<Character> characters) {
this.id = id;
this.apiKey = apiKey;
this.username = username;
this.password = password;
this.role = role;
this.characters = characters;
}
이렇게 되면 순서 또한 기억해서 생성자를 만들어야한다.
이러한 문제는 위에 보이는 코드 뿐만 아니라
팩토리 메소드 패턴이나 정적 팩토리 메소드 패턴등 다른 디자인 패턴에서도 발생하는 이슈들이다.
📗 어떻게 할까?
빌더 패턴은 이러한 문제들을 해결하기 위해 별도의 Builder 클래스를 만들어 필수 값에 대해서는 생성자를 통해,
선택적인 값들에 대해서는 메소드를 통해 step-by-step으로 값을 입력받은 후에 build() 메소드를 통해
최종적으로 하나의 인스턴스를 return하는 방식이다.
여기서 Spring boot 환경이라면 Lomok 어노테이션으로 쉽게 할 수 있다.
아래는 Builer 패턴을 반영한 코드이다
[Member]
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Member extends BaseTimeEntity {
/**
* 회원 목록 테이블
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private long id;
@Column(unique = true, nullable = false, length = 1000)
private String apiKey;
@Column(unique = true)
private String username;
private String password;
@Enumerated(EnumType.STRING)
private Role role;
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
private List<Character> characters = new ArrayList<>();
}
기본적인 @NoArgsConstructor와 @AllArgsConstructor만 추가하고,
@Builder 어노테이션을 추가한다.
보시다시피 코드상에 다른 어떠한 생성자도 존재하지 않는다.
[회원가입 메소드]
public Member signup(MemberSignupDto signupDto, List<Character> characterList) {
Member member = Member.builder()
.username(signupDto.getUsername())
.password(passwordEncoder.encode(signupDto.getPassword()))
.apiKey(signupDto.getApiKey())
.characters(characterList)
.role(Role.USER)
.build();
return memberRepository.save(member);
}
회원가입 메소드에서 빌더 패턴을 이용하여 필요한 객체를 생성한다.
이렇게 코드를 작성하면 위에서 얘기한 이슈들이 모두 해결된다.