[JAVA] BuilderPattern

jkky98·2024년 7월 5일

Java

목록 보기
33/51

생성자에 대한 불만

def greet(name, greeting="Hello"):
    print(f"{greeting}, {name}!")

# 기본값 사용
greet("Alice")  # 출력: Hello, Alice!

# 매개변수 값을 직접 지정
greet("Bob", "Good morning")  # 출력: Good morning, Bob!

위는 파이썬의 선택적 매개변수 개념을 보이기 위한 코드이다. 파이썬의 경우 함수 작성시 설계해야하는 매개변수 부분에 default를 넣을 수 있다. 함수 설계 후 함수를 사용할 때(클래스의 초기화도 마찬가지) 디폴트가 설정된 매개변수에는 값을 넣어주지 않으면 디폴트가 반영되어 작동한다.

자바의 경우

자바의 생성자는 이러한 지점에서 불편한 점이 있다. 매개변수의 디폴트를 직접 설정할 수 없기 때문이다.

자바의 클래스에 멤버가 작성되고 이 멤버를 초기화 하기 위해 생성자를 작성한다면 클래스로 인스턴스를 호출할 때 생성자에 input으로 적은 파라미터를 모두 작성해주어야 한다.

만약 생성자가 다르게 동작하기를 원한다면 우리는 생성자를 또 작성해주어야 한다. 파이썬은 이러한 점에서 하나의 ()안에서 처리할 수 있다는 것이 직관적이고 편리하다.

만약 멤버가 많고 요구사항 변경이 많아 생성자가 여러개 필요한 경우라면 생성자로만 도배된 코딩화면을 봐야할지도 모른다. 가독성이 굉장히 떨어질 것이다.
이러한 생성자 패턴을 점층적 생성자 패턴이라고 한다.(흔하게 많이 사용했던 것 같다. 생각보다 생성자가 많이 필요할 경우가 없어서일까.)

public class Pizza {
    private final int pizza;
    private final boolean vegetarian;

    private final int pieces;
    private final boolean pickle;
    private final boolean colaToZero;
    private final int hotSauce;

    // 생성자 1: 모든 매개변수를 받아 초기화
    public Pizza(int pizza, boolean vegetarian, int pieces, boolean pickle, boolean colaToZero, int hotSauce) {
        this.pizza = pizza;
        this.vegetarian = vegetarian;
        this.pieces = pieces;
        this.pickle = pickle;
        this.colaToZero = colaToZero;
        this.hotSauce = hotSauce;
    }

    // 생성자 2: 기본적인 피자 정보와 조각 수를 받아 초기화, 기본값 설정
    public Pizza(int pizza, int pieces) {
        this(pizza, false, pieces, false, false, 0); // vegetarian, pickle, colaToZero, hotSauce 기본값 설정
    }

    // 생성자 3: 피자 종류와 조각 수만 받아 초기화, 나머지 기본값
    public Pizza(int pizza, int pieces, boolean vegetarian) {
        this(pizza, vegetarian, pieces, false, false, 0); // pickle, colaToZero, hotSauce 기본값 설정
    }

    // 생성자 4: 기본 피자, 조각 수만 받아 초기화, 나머지 기본값
    public Pizza(int pieces) {
        this(1, false, pieces, false, false, 0); // pizza (기본값: 1), vegetarian (기본값: false), pickle, colaToZero, hotSauce 기본값 설정
    }

    // toString 메서드 (객체의 문자열 표현을 반환)
    @Override
    public String toString() {
        return "Pizza{" +
                "pizza=" + pizza +
                ", vegetarian=" + vegetarian +
                ", pieces=" + pieces +
                ", pickle=" + pickle +
                ", colaToZero=" + colaToZero +
                ", hotSauce=" + hotSauce +
                '}';
    }

    // main 메서드를 사용하여 생성자 테스트
    public static void main(String[] args) {
        // 다양한 생성자 사용 예시
        Pizza pizza1 = new Pizza(1, true, 8, true, true, 2);
        Pizza pizza2 = new Pizza(2, 6);
        Pizza pizza3 = new Pizza(3, 10, false);
        Pizza pizza4 = new Pizza(12);

        // 객체 정보 출력
        System.out.println(pizza1);
        System.out.println(pizza2);
        System.out.println(pizza3);
        System.out.println(pizza4);
    }
}

Builder Pattern(빌더 패턴)

빌더 패턴은 생성자 패턴의 안전성과 자바 빈즈 패턴의 가독성을 겸비한다. 바로 코드를 보자.

package hello.core.pattern.builder;

public class BuilderPattern {
    private final int pizza;
    private final boolean vegeterian;

    private final int pieces;
    private final boolean pickle;
    private final boolean colaToZero;
    private final int hotsauce;

    public static class Builder {
        private final int pizza;
        private final boolean vegeterian;

        private int pieces=8;
        private boolean pickle=true;
        private boolean colaToZero=false;
        private int hotsauce=1;

        public Builder(int pizza, boolean vegeterian) {
            this.pizza = pizza;
            this.vegeterian = vegeterian;
        }

        public Builder pieces(int val) {
            pieces = val;
            return this;
        }

        public Builder pickle(boolean bool) {
            pickle = bool;
            return this;
        }

        public Builder colaToZero(boolean bool) {
            colaToZero=bool;
            return this;
        }

        public Builder hotsauce(int val) {
            hotsauce = val;
            return this;
        }

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

    private BuilderPattern(Builder builder) {
        pizza = builder.pizza;
        vegeterian = builder.vegeterian;

        pieces = builder.pieces;
        pickle = builder.pickle;
        colaToZero = builder.colaToZero;
        hotsauce = builder.hotsauce;

    }

    public BuilderPattern(int pizza, boolean vegeterian, int pieces, boolean pickle, boolean colaToZero, int hotsauce) {
        this.pizza = pizza;
        this.vegeterian = vegeterian;
        this.pieces = pieces;
        this.pickle = pickle;
        this.colaToZero = colaToZero;
        this.hotsauce = hotsauce;
    }
}

정적 내부 클래스로 Builder를 작성한다. 이 Builder는 부모 클래스의 멤버를 그대로 가지나, 필수 멤버는 final로, 선택 멤버는 final없이 작성하고 디폴트 값을 부여한다. 그리고 필수 멤버는 생성자로 초기화하며, 선택 멤버는 set메서드를 만들어 필요시 값을 수정한다.

메서드 체이닝을 이용하기 위해(이용의 이유는 아래에 있다)

그러므로 선택멤버가 수정되었다면 새로운 객체자신을 리턴해야한다.

그리고 build 메서드로 부모 클래스의 new 인스턴스(Builder)를 리턴한다. 생성자에 부모 클래스의 멤버들의 후보가 아닌 어떤 매개체, Builder가 들어왔다.

위와 같이 설계할 경우 다음과 같은 코딩이 가능해진다.

BuilderPattern pizza = new BuilderPattern.Builder(1, false)
						.pickle(false)
                        .colaToZero(true)
                        .hotsauce(3)
                        .build()

굉장히 직관적이고 가독성이 좋은, 디폴트를 부여한 선택멤버에 대해 생성 메서드를 호출하지 않아도 디폴트가 알아서 적용되고 있다.

만약 메서드 체이닝이 적용되지 않는다면 다음처럼 작성해야할 것이다.

Builder builder = new Builder(1, true);
builder.pieces(10);
builder.pickle(true);
builder.colaToZero(true);
builder.hotsauce(2);

BuilderPattern result = builder.build();

가독성 면에서 메서드 체이닝을 이용하는 것이 훨씬 직관적이다.

의문점

다만 설계관점에서 코드가 매우 길어진다. 빌더 패턴이 파이썬의 선택적 매개변수 패턴을 위시해서 만들어졌다고 했지만 설계 과정의 코딩이 어려우니 후에 유지보수시 읽기에도 어렵지 않을까 싶었다. 하지만 가져와서 사용하는 것에서는 일반 생성자 방식보단 직관적인데, 그러나 사실 인텔리제이가 요즘은 생성자에 들어올 파라미터에 대해 파라미터명을 작게 띄워줘서 가독성 부분에서의 장점에서도 의문이 든다. 다만 위의 경우 멤버변수 6개에 대한 모든 생성자 경우의 수를 포괄하고 있기 때문에, 생성자가 매우 많아질 상황이라면 빌더 패턴은 아주 유용할 듯 하다.

profile
자바집사의 거북이 수련법

0개의 댓글