- 선택적 매개변수가 많을 때 적절히 대응하기가 어려움
public class User {
private final int age; //필수
private final int height; //필수
private final int weight; //필수
private final int yearJoined; //선택
private final int level; //선택
private final int score; //선택
}
필수 매개변수를 받는 생성자, 필수 매개변수와 선택 매개변수 1개를 받는 생성자, 필수 매개변수와 선택 매개변수 2개를 받는 생성자… 형태로 모든 매개변수를 받을 때 까지 늘려가는 방식
public class User {
private final int age; //필수
private final int height; //필수
private final int weight; //필수
private final int yearJoined; //선택
private final int level; //선택
private final int score; //선택
public User(int age, int height, int weight, int yearJoined) {
this(age, height, weight, yearJoined, 0);
}
public User(int age, int height, int weight, int yearJoined, int level) {
this(age, height, weight, yearJoined, level, 0);
}
public User(int age, int height, int weight, int yearJoined, int level, int score) {
this.age = age;
this.height = height;
this.weight = weight;
this.yearJoined = yearJoined;
this.level = level;
this.score = score;
}
}
점층적 생성자 패턴은 매개변수가 많아질 수록 코드를 작성하거나 읽기 어려움
User user = new User(26,180, 75, 2024, 0, 40);
- 각 값의 의미가 헷갈림
- 매개변수의 개수를 주의깊게 살펴야함
- 타입이 같은 매개변수의 순서가 바뀌게 되면 컴파일 단계에서 찾기 어려움
매개변수가 없는 생성자로 객체를 만든 후, 세터(setter) 메서드를 호출해 값을 설정하는 방식
public class User {
private int age;
private int height;
private int weight;
private int yearJoined;
private int level;
private int score;
public User() {
}
public void setAge(int age) {
this.age = age;
}
public void setHeight(int height) {
this.height = height;
}
public void setWeight(int weight) {
this.weight = weight;
}
public void setYearJoined(int yearJoined) {
this.yearJoined = yearJoined;
}
public void setLevel(int level) {
this.level = level;
}
public void setScore(int score) {
this.score = score;
}
}
자바빈즈 패턴은 점층적 패턴의 단점을 해결하고 좀 더 읽기 쉬운 코드를 만듬
User user = new User();
user.setAge(25);
user.setHeight(180);
user.setWeight(75);
user.setYearJoined(2020);
user.setLevel(5);
user.setScore(100);
자바빈즈 패턴의 단점
- 객체를 만들기 위해서 여러개의 메서드를 호출해야함
- 객체가 완전히 생성되기 전에는 일관성이 무너진 상태가 됨
- 일관성이 없기 때문에 객체를 불변으로 생성할 수 없음
cf) 일관성이 무너진다의 의미
- 객체의 상태가 예측할 수 없게 변함
- 객체가 불완전한 상태에 놓일 수 있음
필수 매개변수만으로 생성자를 호출해 빌더 객체를 얻어 원하는 선택 매개변수를 설정한 뒤 build 메서드를 호출해 객체를 얻음
public class User {
private final int age;
private final int height;
private final int weight;
private final int yearJoined;
private final int level;
private final int score;
private User(Builder builder) {
this.age = builder.age;
this.height = builder.height;
this.weight = builder.weight;
this.yearJoined = builder.yearJoined;
this.level = builder.level;
this.score = builder.score;
}
public static class Builder {
private int age;
private int height;
private int weight;
//선택 매개변수를 기본값으로 초기화
private int yearJoined = 0;
private int level = 0;
private int score = 0;
public Builder() {
}
public Builder age(int age) {
//각 변수를 set 하기전 검증 가능
this.age = age;
return this;
}
public Builder height(int height) {
this.height = height;
return this;
}
public Builder weight(int weight) {
this.weight = weight;
return this;
}
public Builder yearJoined(int yearJoined) {
this.yearJoined = yearJoined;
return this;
}
public Builder level(int level) {
this.level = level;
return this;
}
public Builder score(int score) {
this.score = score;
return this;
}
public User build() {
return new User(this);
}
}
}
- 점층적 생성자 패턴의 안정성 + 자바빈즈 패턴의 가독성을 모두 가짐
- 빌더의 세터 메서드들은 빌더 자신을 반환하기 때문에 연쇄적으로 호출 할 수 있음
- 이런 방식을 플루언트API 혹은 메서드 연쇄라 함
User user = new User.Builder()
.age(30)
.height(175)
.weight(70)
.yearJoined(2022)
.level(5)
.score(100)
.build();
public abstract class Pizza{
public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
final Set<Topping> toppings;
// 추상 클래스는 추상 Builder를 가진다. 서브 클래스에서 이를 구체 Builder로 구현한다.
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
// 하위 클래스는 이 메서드를 overriding하여 this를 반환하도록 해야 한다.
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}
public class NyPizza extends Pizza {
public enum Size { SMALL, MEDIUM, LARGE }
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override public NyPizza build() {
return new NyPizza(this);
}
@Override protected Builder self() { return this; }
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
public class Calzone extends Pizza {
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false;
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override public Calzone build() {
return new Calzone(this);
}
@Override protected Builder self() { return this; }
}
private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
}
추상 클래스와 추상 빌더를 생성하여 구체 클래스는 구체 빌더를 가지게 함
NYPizza pizza = new NYPizza.Builder(SMALL)
.addTopping(SAUSAGE)
.addTopping(ONION)
.build();
Calzone calzone = new Calzone.Builder()
.addTopping(HAM)
.sauceInside()
.build();
- 빌더를 이용하면 가변인수 매개변수를 여러 개 사용할 수 있음 ex) addTopping
- 빌더 하나로 여러 객체를 순회하면서 만들 수 있고, 빌더에 넘기는 매개변수에 따라 다른 객체를 만들 수도 있음
- 객체를 만들기 위해서는 빌더부터 만들어야함
- 추가되는 코드가 많아 매개변수 4개 이상부터 값어치를 함
- 생성자나 정적 팩터리 메서드가 처리해야할 메서드가 많거나, 매개변수 중 다수가 필수가 아니라면 빌더 패턴을 선택하는 것이 나음
- 빌더는 점층적 생성자보다 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 안전함