필수 매개변수를 받는 생성자를 먼저 생성하고, 선택 매개변수 1개를 추가로 받는 생성자, 선택 매개변수 2개...3개..4개.. 추가로 받는 생성자 등의 형태로 매개변수 개수만큼 생성자를 늘리는 방식이다.
마치 생성자가 점층적으로 성장하는 생성자를 가지도록 한 디장인 패턴이 '점층적 생성자 패턴' 이다.
public class User {
private final String name; // 필수
private final int age; // 필수
private final String phone; // 선택
private final String address; // 선택
// #1
public User(String name, int age, String phone, String address) { ...}
// #2
public User(String name, int age) { ...}
public User(String name, int age, String phone) { ...}
public User(String name, int age, String phone, String address) { ...}
}
// #1 파라미터 인자 값으로 null을 전달한다.
User user0 = new User("ssong", 28, null, null);
// #2 원하는 매개변수를 포함한 생성자를 매번 작성해야 한다.
User user1 = new User("ssong", 28);
User user2 = new User("ssong", 28, "010-1234-5678");
User user3 = new User("ssong", 28, "010-1234-5678", "서울시 ...");
결론은 생성자 패턴도 쓸 수 있지만, 매개변수 개수가 많아지면 코드를 작성하거나 읽기 어렵다.
그래서 대안으로 쓰는 방법이 JavaBeans Pattern이다.
매개변수가 없는 생성자로 객체를 만든 후,
Setter
메서드를 호출해 매개변수의 값을 설정하는 방식
public class User {
// non final attribute
private String name;
private int age;
private String phone;
private String address;
public User() {}
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
public void setPhone(String phone) { this.phone = phone; }
public void setAddress(String address) { this.address = address; }
}
User user = new User();
user.setName("ssong");
user.setAge(28);
user.setPhone("010-1234-5678");
user.setAddress("서울시 ...")
생성자 패턴의 단점들이 자바빈즈 패턴에서는 보이지 않는다.
Setter
메서드 때문에 코드가 길어지고 작성하기 번거로울 것 같지만 Generate 또는 lombok의 @Setter
를 활용하면 해결된다.
Setter
를 통해 내용을 변경할 수 있다.Freeze
메서드를 사용하는 방법이 있지만 이 또한 다루기가 어렵고 런타임 오류에 취약해서 권장하지 않는다.이제 점층적 생성자 패턴과 바자빈즈 패턴에서의 장점만 가져온 Builder 패턴을 알아보자
빌더 패턴은 복잡한 객체를 생성하는 방법을 정의하는 과정과 표현하는 방법을 정의하는 과정을 별도로 분리하여, 서로 다른 표현이라도 이를 생성할 수 있는 동일한 절차를 제공하는 패턴이다.
클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻는다.
그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수들을 설정한다.
마지막으로 매개변수가 없는 build 메서드를 호출해 드디어 우리에게 필요한 객체를 얻는다.
public class User
{
// final attribute
private final String name; // 필수
private final int age; // 필수
private final String phone; // 선택
private final String address; // 선택
private User(UserBuilder builder) {
this.name = builder.name;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
// Getter만 존재하므로 불변성을 제공함.
public String getName() { return name; }
public int getAge() { return age; }
public String getPhone() { return phone; }
public String getAddress() { return address; }
public static class UserBuilder
{
private final String name; // 필수
private final int age; // 필수
private String phone; // 선택
private String address; // 선택
public UserBuilder(String name, int age) {
this.name = name;
this.age = age;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
// 마지막으로 build()를 통해 객체 반환
public User build() {
User user = new User(this);
validateUserObject(user);
return user;
}
private void validateUserObject(User user) {
// 확인을 위해 기본 유효성 검사를 수행한다.
}
}
}
User user1 = new User.UserBuilder("ssong", 28)
.build();
User user2 = new User.UserBuilder("ssong", 28)
.phone("010-1234-5678")
// no address
.build();
User user3 = new User.UserBuilder("ssong", 28)
.phone("010-1234-5678")
.address("서울시 ...")
.build();
@Builder
를 사용하면 해결이 가능하다.lombok의 @Builder는 GoF의 Builder Pattern과는 조금 다르다고 하니
lombok 빌더패턴의 Vanilla Java Code는
https://projectlombok.org/features/Builder 에서 확인해보자..