Joshua Bloch의 빌더 디자인 패턴이 개선 되었습니까?


12

2007 년에 Joshua Blochs가 "빌더 패턴"에 대해 다루는 기사와 특히 개체에 많은 속성이있는 경우 생성자 및 세터의 남용을 개선하기 위해 수정하는 방법에 대한 기사를 읽었습니다. 대부분은 선택 사항입니다. 이 디자인 패턴에 대한 간략한 요약이 여기에 있습니다 .

나는 그 아이디어를 좋아했고 그 이후로 그것을 사용해왔다. 그것의 문제는 클라이언트 관점에서 사용하는 것이 매우 깨끗하고 훌륭하지만 그것을 구현하는 것은 부끄러운 일이 될 수 있습니다! 객체에는 단일 속성이 참조되는 많은 다른 위치가 있으므로 객체를 만들고 새 속성을 추가하는 데 많은 시간이 걸립니다.

그래서 ... 아이디어가있었습니다. 먼저 Joshua Bloch 스타일의 예제 객체 :

조쉬 블로흐 스타일 :

public class OptionsJoshBlochStyle {

    private final String option1;
    private final int option2;
    // ...other options here  <<<<

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private String option1;
        private int option2;
        // other options here <<<<<

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

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

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

    private OptionsJoshBlochStyle(Builder builder) {
        this.option1 = builder.option1;
        this.option2 = builder.option2;
        // other options here <<<<<<
    }

    public static void main(String[] args) {
        OptionsJoshBlochStyle optionsVariation1 = new OptionsJoshBlochStyle.Builder().option1("firefox").option2(1).build();
        OptionsJoshBlochStyle optionsVariation2 = new OptionsJoshBlochStyle.Builder().option1("chrome").option2(2).build();
    }
}

이제 "향상된"버전 :

public class Options {

    // note that these are not final
    private String option1;
    private int option2;
    // ...other options here

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private final Options options = new Options();

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

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

        public Options build() {
            return options;
        }
    }

    private Options() {
    }

    public static void main(String[] args) {
        Options optionsVariation1 = new Options.Builder().option1("firefox").option2(1).build();
        Options optionsVariation2 = new Options.Builder().option1("chrome").option2(2).build();

    }
}

내 "개선 된 버전"에서 볼 수 있듯이 추가 속성 (또는이 경우 옵션)에 대한 코드를 추가해야하는 곳이 2 개 더 적습니다! 내가 볼 수있는 유일한 부정적인 점은 외부 클래스의 인스턴스 변수가 최종적이 될 수 없다는 것입니다. 그러나 클래스는 이것 없이는 여전히 불변입니다.

이러한 유지 보수성 개선에 대한 단점이 있습니까? 그가 보지 못한 중첩 클래스 내에서 속성을 반복 한 이유가 있어야합니까?


이것은 C #에서 빌더 패턴의 필자와 매우 유사 보인다 여기 .
MattDavey 2013

답변:


12

당신의 변형은 아주 좋습니다. 그러나 사용자가이를 수행 할 수 있습니다.

Options.Builder builder = new Options.Builder().option1("firefox").option2(1);
Options optionsVariation1 = builder.build();
assert optionsVariation1.getOption1().equals("firefox");
builder.option1("chrome");
assert optionsVariation1.getOption1().equals("firefox"); // FAILURE!

오히려 객체를 물리칩니다.

이를 위해 build방법을 변경할 수 있습니다 .

public Options build() {
    Options options = this.options;
    this.options = null;
    return options;
}

이것은 이것을 막을 것입니다-호출 후 빌더에서 setter 메소드에 대한 build호출은 NullPointerException으로 실패합니다. 플래시하고 싶다면 null을 테스트하고 대신 IllegalStateException 또는 무언가를 던질 수 있습니다. 그리고 모든 빌더에서 사용할 수있는 일반 기본 클래스로이를 이동할 수 있습니다.


1
두 번째 줄 build()을 다음으로 변경합니다 this.options = new Options();. 이런 방식으로 Options 인스턴스는 안전하게 변경할 수 없으며 빌더는 동시에 재사용 할 수 있습니다.
Natix

5

Bloch 패턴의 빌더는 "주로"동일한 오브젝트를 생성하는 데 여러 번 사용될 수 있습니다. 또한 변경 불가능한 객체 (모든 필드는 최종적이고 자체적으로 변경 불가능)는 변경 사항이 무효화 될 수있는 스레드 안전성 이점을 제공합니다.


0

Options가 효과적으로 복제 가능하다면 (Cloneable 인터페이스와 상관없이) 프로토 타입 패턴을 사용할 수 있습니다. 빌더에서 하나를 갖고 build ()에서 복제하십시오.

Cloneable 인터페이스를 사용하지 않으면 모든 필드를 복사해야하므로 추가해야 할 다른 장소를 추가해야하므로 최소한 Cloneable을 사용하는 간단한 필드가있는 클래스의 경우 좋은 아이디어입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.