목차
1. 빌더(builder) 패턴이란?
- 빌더 패턴이란, 디자인 패턴 중 생성 패턴의 한 종류로 다양한 구성의 인스턴스를 만드는데 유용하다.
예를 들어, 아래와 같은 class가 있다고 가정해보자.
@AllArgsConstructor
public class User {
private String name;
private int age;
private String address;
}
이 때, 만약 요구사항이 바뀌어 "address 없이 User객체 만들 수 있게 해주세요!"
라는 요청이 들어오게 된다면? 아.. 귀찮아
@AllArgsConstructor
public class User {
private String name;
private int age;
private String address;
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
결국 직접 원본 클래스에 address를 뺀 생성자를 만들어 줄 수 밖에 없다.
그런데 또 "어.. 죄송한데 age도 없이 만들 수 있게 해주세요 ㅎㅎ"
아..
이럴 때 미리 빌더패턴을 이용해 구현해 놓았다면?
User user1 = User.builder()
.name("김씨")
.age("21")
.build()
User user2 = User.builder()
.name("이씨")
.build()
원본 클래스의 수정없이 간단하게 요구사항에 대응할 수 있다.
또한 코드의 가독성 또한 훨씬 뛰어나다.
2. 구현 코드 및 사용 예시
- Member.java(lombok 미사용/직접구현)
public class Member {
private String name;
private int age;
private String address;
private String phone;
private Member(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.address = builder.address;
this.phone = builder.phone;
}
// 빌더 호출, 외부에서 Member.builder() 으로 접근할 수 있도록 static 메소드로 생성
public static Builder builder() {
return new Builder();
}
// static 형태의 inner class 생성
public static class Builder {
private String name;
private int age;
private String address;
private String phone;
private Builder() {};
public Builder name(String name) {
this.name = name;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
// 마지막에 build 메소드를 실행하면 this가 return 되도록 구현
public Member build() {
return new Member(this);
}
}
}
출처 : https://wildeveloperetrain.tistory.com/30
빌더를 별다른 어노테이션 없이 순수하게 구현한 코드이다. innerClass를 만들어 사용한다.
하지만 코드가 길고 구현이 번거로워 보통 이렇게 직접 구현해 사용하진 않고, 아래의 코드처럼 사용한다.
-Member.java(lombok 사용)
@Builder
@AllArgsConstructor
public class Member {
private String name;
private int age;
private String address;
private String phone;
}
롬복을 사용해 간결하게 빌더 패턴을 이용할 수 있다.
그런데 만약 필수 파라미터가 존재하는 경우라면?
- Member.java
@NoArgsConstructor
@Entity
@Table(name="memberinfo_tb")
public class Member implements UserDetails {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id", nullable = false)
private String userId;
@Column(name = "user_password", nullable = false)
private String userPassword;
@Column(name = "user_ip")
private String userIp;
@Column(name = "last_login")
private String lastLogin;
@Column(name = "create_date", nullable = false)
private String createDate; // create_date 컬럼은 자동생성
private String roles;
// 필수 파라미터 설정 && 빌더 패턴 사용
@Builder
public Member(String userId, String userPassword, String roles) throws IllegalArgumentException {
// 안전한 객체 생성을 위한 검증 (빈 값이 들어올 시 에러내기 위하여)
Assert.hasText(userId, "userId mut not be empty!");
Assert.hasText(userPassword, "userPassword mut not be empty!");
Assert.hasText(roles, "roles mut not be empty!");
// Enum Roles 에 정의된 값만 가짐
String role = Roles.valueOf(roles).toString();
// 현재 일자를 형식에 맟춰 생성일자로 삽입
SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date time = new Date();
this.createDate = dateTimeFormat.format(time);
this.userId = userId;
this.userPassword = userPassword;
this.roles = role;
}
위 코드는 @Builder 어노테이션을 생성자 위에 붙여서 사용하였는데, 이는 필수 파라미터를 정의하기 위해서이고,
만약 필수 파라미터가 필요없는 경우라면 클래스 위에 @AllArgsConstructor와 함께 @Builder를 사용해 구현 가능하다.
- MemberTest.java(테스트코드)
public class MemberTest {
@Test
public void 아이디없이멤버생성() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
Member.builder()
.userId("")
.userPassword("password")
.roles(Roles.USER)
.build();
});
} // IllegalArgumentException이 발생해야 테스트 통과
@Test
public void 패스워드없이멤버생성() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
Member.builder()
.userId("kiki")
.userPassword("")
.roles(Roles.USER)
.build();
});
} // IllegalArgumentException이 발생해야 테스트 통과
@Test
public void 정상적으로멤버생성() {
Member member = Member.builder()
.userId("kiki")
.userPassword("password")
.roles(Roles.USER)
.build();
Assertions.assertEquals("kiki", member.getUserId());
Assertions.assertEquals("password", member.getPassword());
Assertions.assertEquals("USER", member.getRoles().toString());
}
}
3. 정리 및 결론
나는 클래스를 생성할 때 Builder 패턴을 꼭 사용하는 편이다.
가독성, 유연성이 굉장히 좋아지고, lombok을 사용하지 못하는 특수한 경우가 아닌 이상 구현도 간단하기 때문이다.
다만, 필수 파라미터가 존재하는 클래스에서 사용할 경우 좀 더 주의가 필요하다.
'Language > Java' 카테고리의 다른 글
인터페이스와 추상클래스 (0) | 2024.07.16 |
---|---|
서블릿(Servlet) (1) | 2024.07.16 |
예외(Exception) 처리 (3) | 2024.07.16 |
스트링(string) (0) | 2024.07.16 |
가비지 컬렉션(GC) (0) | 2024.07.16 |