생성자, 정적 팩터리 메서드가 처리해야할 매개변수가 많다면 빌더 패턴을 선택하자
선택 매개변수가 많을 때 생성자를 하나씩 다 만들어야할까? (점층적 생성자)
혹은 하나만 만들고 세터로 값을 설정해야할까? (빈즈)
이러한 문제를 해결할 수 있는 방식인 빌더 패턴에 대해 알아보자
점층적 생성자 패턴
필수 생성자만 입력받는 생성자를 기본적으로 만들고, 선택 매개변수를 n개 받는 생성자를 추가로 만드는 방식
이 경우에는 필요한 생성자를 모두 만들어야하기 때문에 매개변수가 많아지면 가독성이 떨어진다.
class Computer {
private String processor;
private int ram;
private int storage;
// 첫 번째 생성자 (기본값만)
public Computer(String processor) {
this.processor = processor;
}
// 두 번째 생성자 (RAM 추가)
public Computer(String processor, int ram) {
this(processor); // 첫 번째 생성자 호출
this.ram = ram;
}
// 세 번째 생성자 (RAM, Storage 추가)
public Computer(String processor, int ram, int storage) {
this(processor, ram); // 두 번째 생성자 호출
this.storage = storage;
}
}
자바 빈즈 패턴
매개변수가 없는 생성자로 객체를 만들고, setter로 원하는 매개변수 값을 설정하는 방식
점층적 생성자 패턴보다 인스턴스를 만들기 쉽고 가독성도 더 나아졌다.
하지만 객체 하나를 만들기 위해서 여러 setter 메서드를 호출해야하고,
완전히 생성되기 전까지는 일관성을 충족하지 못한다.
import java.io.Serializable;
public class Person implements Serializable {
private String name;
private int age;
// 기본 생성자
public Person() {
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public static void main(String[] args) {
// 자바 빈즈 객체 생성
Person person = new Person();
// Setter를 통해 속성 설정
person.setName("java");
person.setAge(30);
}
}
빌더 패턴
필수 매개 변수만 사용하여 빌더 객체를 얻고, 빌더 객체가 제공하는 setter로 선택 매개변수를 설정한 후
build() 메서드를 호출해 불변 객체를 얻는 방식
1 . 필수 매개 변수만 포함하는 빌더 객체를 생성
new Computer.Builder("Intel i7") // 필수
2. 빌더 클래스 내에서 생성한 선택 매개변수 setter들 호출
.ram(16) // 선택
.storage(512) // 선택
3. build() 메서드 호출하며 불변 객체 생성
public class Computer {
private String processor;
private int ram;
private int storage;
// 기본 생성자
private Computer(Builder builder) {
this.processor = builder.processor;
this.ram = builder.ram;
this.storage = builder.storage;
}
// 빌더 클래스
public static class Builder {
private String processor; // 필수
private int ram; // 선택
private int storage; // 선택
// 필수
public Builder(String processor) {
this.processor = processor;
}
// 선택
public Builder ram(int ram) {
this.ram = ram;
return this;
}
// 선택
public Builder storage(int storage) {
this.storage = storage;
return this;
}
// 객체 반환 메서드
public Computer build() {
return new Computer(this);
}
}
// 메인 메서드
public static void main(String[] args) {
// 빌더 패턴 사용
Computer computer = new Computer.Builder("Intel i7") // 필수
.ram(16) // 선택
.storage(512) // 선택
.build();
}
}
이러한 구조를 통해 세터 메서드를 통해 매개변수 값을 설정할 수 있고, 불변 객체를 최종적으로 만들 수 있다.
빌더 패턴 장점 - 계층적으로 설계된 클래스와 함께 사용하기 좋다.
추상 클래스와 구체 클래스가 있을 때, 각 클래스 위치에 빌더를 생성한다.
각 구체 클래스에서 빌더를 만드는 방식을 사용하면, 필수 매개변수가 달라도 유연하게 사용할 수 있다.
이러한 구조를 계층적 빌더라고 한다.
Pizza 추상 클래스
- 추상 클래스의 Builder은 제네릭 타입으로 생성한다.
- 추상 메서드 self를 추가하여, 구체 클래스에서 메서드 체이닝을 사용할 수 있게 하였다.
public abstract class Pizza {
public enum Topping {HAM, ONINON, PEPPER}
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();
}
}
NyPizza 구체 클래스
- size 매개 변수를 필수로 하는 빌더를 생성한다.
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;
}
}
Calzone 구체 클래스
- 소스를 필수 매개변수로 하는 빌더를 생성한다.
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(HAM).addToping(ONION).build();
Calzone calzone = new Calzone.Builder().addTopping(HAM).sauceInside().build();
빌더 패턴 단점 - 객체를 만들기 위해 빌더를 만들어야한다.
빌더 생성 비용이 크진 않으나, 다른 패턴보다 코드가 길어질 수 있어 매개변수가 4개 이상일 때 좋다.
하지만 매개 변수가 늘어나는 경우가 많으니, 초반부터 빌더를 적용하는 것이 낫다.
'도서 > 이펙티브 자바' 카테고리의 다른 글
| [이펙티브 자바] 6. 불필요한 객체 생성을 피하라 (0) | 2025.03.20 |
|---|---|
| [이펙티브 자바] 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2025.03.04 |
| [이펙티브 자바] 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2025.03.03 |
| [이펙티브 자바] 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2025.02.22 |
| [이펙티브 자바] 1. 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2025.02.20 |