티스토리 뷰

정적 팩터리 메서드와 생성자는 선택적 매개변수가 많을 때 적절히 대응하기 어렵다는 단점이 있다.

💡 대안 1 : 자바빈즈 패턴 (JavaBeans Pattern)

매개변수가 없는 생성자로 객체를 만든 후, setter 메소드를 호출해 매개변수의 값을 설정한다.

public class KongGookSoo {
    String name;
    String address;
    String kindOfBeans;
    boolean isAlways;
    int price;

    // getter, setter..
}
KongGookSoo kgs = new KongGookSoo();
kgs.setName("서민준밀밭");
kgs.setAddress("서울시 영등포구");
kgs.setKindOfBeans("대두");
kgs.setIsAlways(true);
kgs.setPrice(11000);

👍 장점

  1. 인스턴스를 만들기 쉽다.
  2. 읽기 편하다

👎 단점

  1. 객체 하나를 만들기 위해 여러 메서드를 호출해야 한다.
  2. 객체를 완성하기 전까지는 일관성이 무너진 상태에 놓인다.
    • 클래스를 불변으로 만들 수 없으며, 스레드 안정성을 얻기 위한 추가 작업이 필요하다.

💡 대안 2 : 빌더 패턴 (Builder Pattern)

필수 매개변수만으로 생성자(혹은 정적 팩터리)를 호출해 빌더 객체를 얻는다.
그 다음 빌더 객체가 제공하는 setter 메소드를 호출해 매개변수를 설정한다.

public class KongGookSoo {
    private final String name;
    private final String address;
    private final String kindOfBeans;
    private final boolean isAlways;
    private final int price;

    public static class Builder {
        // 필수 매개변수
        String name;
        String address;
        boolean isAlways;

        // 선택매개변수 - 기본값
        String kindOfBeans = "";
        int price = 0;

        public Builder(String name, String address, boolean isAlways) {
            this.name = name;
            this.address = address;
            this.isAlways = isAlways;
        }

        public Builder kindOfBeans(String kindOfBeans) {
            this.kindOfBeans = kindOfBeans;
            return this;
        }

        public Builder price(int price) {
            this.price = price;
            return this;
        }

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

    private KongGookSoo(Builder builder) {
        name = builder.name;
        address = builder.address;
        kindOfBeans = builder.kindOfBeans;
        isAlways = builder.isAlways;
        price = builder.price;
    }
}
KongGookSoo kgs = new KongGookSoo.Builder("콩심팥심", "경기도 김포시", true).kindOfBeans("대두").price(10000).build();

연쇄적으로 호출되므로 플루언트API, 메서드 연쇄(method chaining)이라고도 한다.

계층적으로 설계된 클래스와 함께 쓰기 좋다.

public abstract class Game {
    public enum Genre { FPS, RACING, RPG, SIMULATION, SPORTS, TACTIC }
    final Set<Genre> genres;

    abstract static class Builder<T extends Builder<T>> {
        EnumSet<Genre> genres = EnumSet.noneOf(Genre.class);
        public T addGenre(Genre genre) {
            genres.add(Objects.requireNonNull(genre));
            return self();
        }

        abstract Game build();

        protected abstract T self();
    }

    Game(Builder<?> builder) {
        genres = builder.genres.clone();
    }
}

이것은 게임의 장르를 표현하는 계층구조의 루트에 놓인 추상클래스이다.

public class Overwatch extends Game{
    public enum Client { BLIZZARD, STEAM }
    private final Client client;

    public static class Builder extends Game.Builder<Builder> {

        private final Client client;

        public Builder(Client client) {
            this.client = Objects.requireNonNull(client);
        }

        @Override
        Overwatch build() {
            return new Overwatch(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    private Overwatch(Builder builder) {
        super(builder);
        client = builder.client;
    }
}
public class StardewValley extends Game {

    private final boolean isVanila;

    public static class Builder extends Game.Builder<Builder> {
        private boolean isVanila = true; // 기본값

        public Builder vanila() {
            isVanila = true;
            return this;
        }

        @Override
        StardewValley build() {
            return new StardewValley(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    private StardewValley(Builder builder) {
        super(builder);
        isVanila = builder.isVanila;
    }
}
        Overwatch overwatch = new Overwatch.Builder(Overwatch.Client.BLIZZARD)
                .addGenre(Game.Genre.FPS).addGenre(Game.Genre.TACTIC)
                .build();

        StardewValley stardewValley = new StardewValley.Builder()
                .addGenre(Game.Genre.SIMULATION).addGenre(Game.Genre.RPG)
                .vanila()
                .build();
  • 각 하위 클래스의 빌더가 정의한 builder 메서드는 해당 구체 하위 클래스를 반환하도록 선언한다.
    • Overwatch.BuilderOverwatch 반환, StardewValley.BuliderStardewValley 반환
    • 이것을 공변환 타이핑 (Convariant Return Typing) 이라고 한다.
    • 클라이언트가 형변환에 신경쓰지 않고 빌더를 사용할 수 있다.

👎 단점

객체를 만들기 앞서 빌더부터 만들어야 한다.
매개변수가 4개 이상은 되어야 값어치를 한다. (그러나 API는 시간이 지날수록 매개변수가 많아지는 경향이 있다.)