Design Pattern with Java

4. Builder 패턴(from effective-java)

초코너무조코 2025. 1. 7. 14:57
728x90

1. 기본 구조: Pizza 클래스

Java에서는 객체 생성과 관련된 복잡한 요구 사항을 처리할 때 빌더 패턴(Builder Pattern)을 자주 사용합니다. 특히 Effective Java에서는 객체 생성 시 인자가 많거나 선택적인 경우 빌더 패턴을 통해 객체를 명확하고 안전하게 생성할 수 있다고 강조합니다. 오늘은 Pizza 클래스를 예시로 하여 빌더 패턴을 어떻게 구현하는지 분석하고, 이를 어떻게 적용할 수 있는지에 대해 알아보겠습니다.

 

먼저, Pizza 클래스가 어떤 구조로 설계되어 있는지 살펴봅시다.

public abstract class Pizza {
    public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE };
    final Set<Topping> toppings;

    abstract static class Builder {
        EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);

        public Builder addTopping(Topping topping) {
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }

        public Builder sauceInside() {
            return self();
        }

        abstract Pizza build();
        protected abstract Builder self();
    }

    Pizza(Builder builder) {
        toppings = builder.toppings.clone();
    }

    public String toString() {
        return toppings.toString();
    }
}

이 클래스는 Pizza 객체를 빌드하는 기본 템플릿을 제공합니다. 중요한 부분은 Builder 클래스입니다. 빌더는 피자에 추가될 토핑을 관리하고, addTopping 메서드를 통해 토핑을 추가할 수 있습니다. 이 클래스는 Pizza 클래스의 추상 클래스로, 각 피자 종류별로 Builder 클래스를 상속받아 구체화해야 합니다.

2. NyPizza: 뉴욕 스타일 피자

다음은 NyPizza 클래스입니다. 이 클래스는 Pizza의 한 종류로, 사이즈가 추가된 예시입니다.

public class NyPizza extends Pizza {

    public enum Size { SMALL, MEDIUM, LARGE };
    private final Size size;

    public static class Builder extends Pizza.Builder {
        private final Size size;

        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        }

        public NyPizza build() {
            return new NyPizza(this);
        }

        protected Builder self() {
            return this;
        }
    }

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }
}

NyPizza는 Pizza 클래스를 상속받아, Size라는 새로운 속성을 추가합니다. 중요한 부분은 Builder 클래스에서 Size를 필수로 받도록 설계한 점입니다. Builder에서 사이즈를 설정하지 않으면 NullPointerException이 발생하지 않도록 안전하게 Objects.requireNonNull(size)를 사용합니다.

3. Calzone: 칼존 스타일 피자

Calzone 클래스는 또 다른 피자 종류로, 이 피자는 소스가 피자 안에 들어가게 설계되었습니다. 이 클래스를 살펴보겠습니다.

public class Calzone extends Pizza {

    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder {
        private boolean sauceInside = false;

        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

        public Calzone build() {
            return new Calzone(this);
        }

        protected Builder self() {
            return this;
        }
    }

    private Calzone(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }

    public String toString() {
        return toppings.toString() + " sauceInside: " + sauceInside;
    }
}

Calzone 클래스에서는 sauceInside라는 속성이 추가되어, 피자의 내부에 소스가 들어갈지 여부를 설정할 수 있습니다. Builder 클래스에서 sauceInside 메서드를 호출하여 이 속성을 설정할 수 있습니다. 이처럼 각 피자 종류는 Pizza.Builder 클래스를 상속하여 고유한 속성을 설정할 수 있습니다.

4. PizzaTest: 테스트 코드

마지막으로 PizzaTest 클래스는 실제로 Pizza, NyPizza, Calzone을 빌드하고 출력하는 코드입니다.

public class PizzaTest {

    public static void main(String[] args) {
        Pizza nyPizza = new NyPizza.Builder(Size.SMALL).addTopping(Topping.SAUSAGE)
                .addTopping(Topping.ONION).build();

        Pizza calzone = new Calzone.Builder().addTopping(Topping.HAM).addTopping(Topping.PEPPER)
                .sauceInside().build();

        System.out.println(nyPizza);
        System.out.println(calzone);
    }
}

이 코드에서는 NyPizza와 Calzone 객체를 각각 빌드한 후, 출력합니다. NyPizza.Builder와 Calzone.Builder를 통해 필요한 속성을 설정하고, build() 메서드를 호출하여 객체를 생성합니다. toString() 메서드를 통해 피자의 토핑과 특성을 출력할 수 있습니다.

5. 결론

이번 분석을 통해 Effective Java에서 권장하는 빌더 패턴을 실제로 어떻게 구현할 수 있는지 살펴보았습니다. 이 패턴은 다양한 인자가 있을 때 객체 생성의 복잡도를 줄여주며, 코드를 읽는 사람이 객체 생성 과정에서 무엇을 설정할 수 있는지 명확히 알 수 있도록 도와줍니다. 특히 피자와 같이 여러 가지 속성을 가진 객체를 만들 때 빌더 패턴을 활용하면 매우 유용합니다.

Java에서 빌더 패턴을 사용할 때는 Abstract Builder를 정의하고, 이를 상속하여 구체적인 객체를 빌드하는 방식이 가장 일반적입니다. 이를 통해 객체 생성의 유연성과 가독성을 높일 수 있습니다.

피자 객체를 만들 때, 다양한 속성(토핑, 소스 포함 여부, 사이즈 등)을 설정하고, 최종적으로 객체를 생성하는 과정을 잘 보여주는 예시로 이 코드를 이해할 수 있었습니다.

728x90