정적 팩터리 메서드와 생성자에는 같은 제약이 있다.
매개변수의 변동이 많을 때 적절히 대응하기 어려워 지는 것이다.
프로그래머들은 이럴 때 점층적 생성자 패턴을 즐겨 사용했다.
점층적 생성자 패턴이란 쉽게 말해 여러개의 생성자를 만드는 것이다.
매개변수가 다른 생성자들을 만들어 각각의 상황에 대응하지만 한계가 명확히 보인다.
코드가 복잡해지는 것 뿐만 아니라 매개변수가 추가되거나 변경될 때 마다 생성자를 변경해줘야 한다.
그 다음은 getter/setter를 사용하는 자바 빈즈 패턴이 있다.
new로 객체를 생성하고, setter를 이용해 값을 넣어줄 수 있다.
하지만 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.
값이 유효한지 알 수 없고, 값을 어디까지 설정해줘야 하는지 알 수 없다.
빌더 패턴은 점층적 생성자 패턴의 안전성과 자바 빈즈 패턴의 가독성을 가지고 있다.
빌더는 보통 생성할 클래스 안에 정적 클래스로 만들어 둔다.
@Getter
@ToString
public class FileDescription {
public enum WriterTypes {a, b, c, d, e}
private final String path;
private final String directory;
private final String filename;
@Builder(toBuilder = true)
public FileDescription(String directory, String filename) {
this.directory = directory;
this.filename = filename;
this.path = directory+filename ;
}
public static FileDescription newInstance(WriterTypes writerTypes, LogDescription description) {
FileDescriptionBuilder builder = FileDescription.builder();
builder.directory("/c")
);
switch (writerTypes) {
case a -> builder.filename("a");
case b -> builder.filename("b");
case c -> builder.filename("c");
case d -> builder.filename("d");
default -> builder.filename("e");
}
return builder.build();
}
프로젝트 중 사용한 builder와 정적 팩터리 메서드이다.
아이템 1에서 확인했던 정적 팩터리 메서드를 builder와 같이 사용하면 이런 방식으로 사용 할 수 있게 된다.
FileDescription file = FileDescription.builder().directory("a").filename("a").build();
위는 빌더 패턴으로 FileDescription 객체를 생성하는 방법이다.
이 빌더의 클라이언트 코드는 불변이고, 쓰기 쉽고, 읽기 쉽다. 이것이 빌더를 사용하는 이유다.
@toBulider 어노테이션을 true로 설정해주면 기존 값에서 값을 변경한 새로운 객체를 만들 수 있다. ( 같은 객체 x )
빌더 패턴은 계층적으로 설계된 클래스와 함께 사용하기에 좋다.
추상클래스에 빌더를 만들고, 각 하위 클래스의 빌더가 정의한 빌더는 해당하는 하위 클래스를 반환하도록 선언한다.
public abstract class Pizza {
public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
final Set<Topping> toppings;
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();
protected abstract T self();
}
Pizza(Builder<?> builder){
toppings = builder.toppings.clone();
}
}
public class NewYorkPizza extends Pizza {
private final Size size;
public enum Size {
SMALL,
MEDIUM,
LARGE
}
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(final Size size) {
this.size = Objects.requireNonNull(size);
}
@Override
public NewYorkPizza build() {
return new NewYorkPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
NewYorkPizza(final Builder builder) {
super(builder);
size = builder.size;
}
}
이렇게 추상클래스에 enum과 빌더를 적용 시킬 수 있다.
! 하지만 빌더 패턴은 객체를 만들기 전, 빌더를 생성해야 한다.
빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수도 있다.
기존 프로젝트에도 생성자보다는 빌더를 주로 활용했지만 왜? 어떠한 장점 때문에? 라는 것은 정확하게 알고 있지 않았다.
코드가 간결해지고 불변조건이 생긴다 정도만 알고 있었지만, 자세히 알게되니 앞으로 더욱 잘 활용 할 수 있을 것 같다.
역시 알고 쓰는 것과 모르고 쓰는 건 천지차이 인 것 같다..
'JAVA > 이펙티브 자바' 카테고리의 다른 글
5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2023.06.11 |
---|---|
4. 인스턴스화를 막으려면 private 생성자를 사용하라 (0) | 2023.06.10 |
3. private 생성자나 열거타입으로 싱글턴을 보증하라 (0) | 2023.06.10 |
1. 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2023.06.07 |
HashMap이란? (0) | 2021.08.26 |