객체의 생성과 표현을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴
필수 입력 필드 + 선택 입력 필드를 하나씩 늘려가며 생성자를 오버로딩하는 방식
public class Member {
// 필수 입력 정보
private String name;
private int age;
// 선택 입력 정보
private String phoneNumber;
private String email;
}
클래스를 생성하다 보면 위 경우처럼 값이 무조건 있어야 하는 필수 입력 필드와, 값이 있어도, 없어도 되는 선택 입력 필드가 존재하게 된다.
public class Member {
...
public Member() {}
// 필수 입력 정보 생성자
public Member(String name, int age) {
this.name = name;
this.age = age;
}
// 필수 입력 정보 + 전화번호 생성자
public Member(String name, int age, String phoneNumber) {
this.name = name;
this.age = age;
this.phoneNumber = phoneNumber;
}
// 필수 입력 정보 + 이메일 + 전화번호 생성자
public Member(String name, int age, String phoneNumber, String email) {
this.name = name;
this.age = age;
this.phoneNumber = phoneNumber;
this.email = email;
}
}
생성자를 통해 값을 입력받는 경우, 필수 정보를 입력받는 생성자, 필수 입력 정보 + 선택 정보 1을 입력받는 생성자, 필수 입력 정보 + 선택 정보 2를 입력받는 생성자, ... 와 같이 경우에 따른 생성자를 별도로 생성해야 한다. 이는 한 클래스 내의 필드 수가 증가할수록 생성자의 수 또한 증가하여 가독성이 떨어지게 된다.
Member member1 = new Member("member1", 10, "010-1234-5678", "member1@naver.com");
Member member2 = new Member("member2", 20, "member2@naver.com", "010-9876-5432");
또한, 각 생성자의 매개변수 순서를 기억해야 한다. 타입이 같은 필드가 여러 개인 경우 순서가 바뀌어도 타입만 일치하면 컴파일 에러가 발생하지 않고 정상적으로 실행되기 때문에 이후 해당 데이터를 사용하는 로직 처리 시 문제가 될 수 있다.
기본 생성자 +
setter()
를 통한 값 입력
public class Member {
// 필수 정보
private String name;
private int age;
// 선택 정보
private String phoneNumber;
private String email;
public Member() {}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public void setEmail(String email) {
this.email = email;
}
}
점층적 생성자 패턴의 단점을 해결하기 위해 자바 빈즈 패턴을 활용할 수 있다. 자바 빈즈 패턴은 생성자를 일일이 만드는 대신 기본 생성자 하나만 만들고, 각 필드에 대한 setter()
메서드를 열어두어 값을 입력받는 방식이다.
Member member = new Member();
member.setName("member");
member.setAge(10);
member.setEmail("member@naver.com");
member.setPhoneNumber("010-1234-5678");
자바 빈즈 패턴을 사용하면 필드의 입력 순서를 기억할 필요 없이, setXXX()
를 통해 각 필드에 맞는 값을 입력할 수 있다. 하지만, 이 방식 또한 setter()
를 모두 정의해야 하기 때문에 가독성이 좋은 편은 아니며 (해당 문제는 Lombok의 @Setter
로 해결 가능), 필수 입력 필드에 대한 값 입력을 필수로 두지 않기 때문에, 누락할 가능성이 존재한다.
또한, setter()
의 가장 큰 문제는 해당 메서드를 통해 어디서든 객체의 데이터를 변경할 수 있다는 것이다. 여러 메서드에서 특정 인스턴스를 호출하여 setter()
로 값을 변경하다 보면, 이후 변경에 대한 추적이 힘들어 유지보수성이 떨어지게 된다. 따라서, setter()
를 무작위로 열어두는 것은 위험을 초래할 수 있다.
별도의
Builer
클래스의 메서드 통해 값을 입력받은 후,build()
를 통해 인스턴스 생성
Builder
클래스 구현
- 기준 클래스와 같은 필드를 가지는 Builder 클래스 구현
- 값을 입력받기 위한 생성자 + 메서드 생성 →
Builder
객체를 리턴하여 체이닝 기법 적용- 최종 인스턴스를 반환하는
build()
메서드 생성
public class Member {
// 필수 정보
private String name;
private int age;
// 선택 정보
private String phoneNumber;
private String email;
// 빌더 클래스
static class MemberBuilder {
private String name;
private int age;
private String phoneNumber;
private String email;
// 필수 입력 필드 : 생성자
public MemberBuilder(String name, int age) {
this.name = name;
this.age = age;
}
public MemberBuilder setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public MemberBuilder setEmail(String email) {
this.email = email;
return this;
}
// 최종 인스턴스 생성
public Member build() {
Member member = new Member();
member.name = name;
member.age = age;
member.email = email;
member.phoneNumber = phoneNumber;
return member;
}
}
}
Member member = new Member
.MemberBuilder("member", 10)
.setPhoneNumber("010-1234-5678")
.build();
생성자를 통해 필수 입력 필드인 name
과 age
의 값을 입력받고, 선택 입력 필드인 phoneNumber
와 email
은 메서드를 통해 선택적으로 값을 입력할 수 있다. 최종적으로 build()
메서드를 호출해야 입력값이 적용된 Member
인스턴스가 정상적으로 반환된다.
@Builder
Lombok에서 제공하는 빌더 클래스 생성 어노테이션 : 클래스 및 생성자 단위에 적용 가능
@Builder
public class Member {
// 필수 정보
private String name;
private int age;
// 선택 정보
private String phoneNumber;
private String email;
}
Member member = Member.builder()
.name("member")
.age(10)
.email("member@naver.com")
.phoneNumber("010-1234-5678")
.build();
➕ 클래스 단위에 @Builder
와 @NoArgsConstructor
를 함께 사용하면 에러 발생 : @Builder
는 생성자가 없는 경우에만 생성자를 자동으로 생성한다. 따라서, @NoArgsConstructor
에 의해 기본 생성자가 생성되거나, 클래스 내에 모든 필드를 가지는 생성자가 아닌 다른 생성자가 있는 경우 모든 필드를 가지는 생성자를 수동으로 생성해주어야 한다. (@AllArgsConstructors
추가)
public class Member {
// 필수 정보
private String name;
private int age;
// 선택 정보
private String phoneNumber;
private String email;
@Builder
public Member(String name, int age, String phoneNumber) {
this.name = name;
this.age = age;
this.phoneNumber = phoneNumber;
}
}
Member member = Member.builder()
.name("member")
.age(10)
//.email("member@naver.com") // 에러 발생 : 생성자에 미포함된 필드
.phoneNumber("010-1234-5678")
.build();
🙇🏻♀️ 참고 1 : [JAVA] 빌더패턴 (Builder Pattern) , @Builder
🙇🏻♀️ 참고 2 : Builder 패턴, @NoArgsConstructor, @AllArgsConstructor