Ulepszenia wzorca projektowego konstruktora Joshua Blocha?

12

W 2007 roku przeczytałem artykuł o tym, jak Joshua Bloch przyjmuje „wzorzec konstruktora” oraz o tym, jak można go zmodyfikować w celu poprawy nadużywania konstruktorów i ustawiaczy, zwłaszcza gdy obiekt ma dużą liczbę właściwości, z których większość jest opcjonalna. Krótkie streszczenie tego wzoru projektowego znajduje się tutaj .

Podobał mi się ten pomysł i od tego czasu go używam. Problem z tym, chociaż jest bardzo czysty i przyjemny w użyciu z perspektywy klienta, wdrożenie go może być uciążliwe! W obiekcie jest tak wiele różnych miejsc, w których pojedyncza właściwość jest odwołaniem, a zatem utworzenie obiektu i dodanie nowej właściwości zajmuje dużo czasu.

Więc ... miałem pomysł. Po pierwsze przykładowy obiekt w stylu Joshua Blocha:

Styl Josha Blocha:

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();
    }
}

Teraz moja „ulepszona” wersja:

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();

    }
}

Jak widać w mojej „ulepszonej wersji”, są 2 miejsca, w których musimy dodać kod dotyczący dowolnych właściwości dodawania (lub opcji, w tym przypadku)! Jedyny minus, jaki widzę, to to, że zmienne instancji klasy zewnętrznej nie mogą być ostateczne. Ale bez tego klasa jest niezmienna.

Czy naprawdę jest jakaś wada tej poprawy w utrzymywalności? Musi istnieć powód, dla którego powtórzył właściwości w zagnieżdżonej klasie, których nie widzę?

Jason Fotinatos
źródło
To wydaje się bardzo podobne do mojego wzorca wzorca konstruktora w C # tutaj .
MattDavey,

Odpowiedzi:

12

Twoja odmiana jest całkiem niezła. Ale pozwala użytkownikom to zrobić:

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!

Który raczej pokonuje obiekt.

Możesz zmienić buildmetodę, aby to zrobić:

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

Co by temu zapobiec - każde wywołanie metody ustawiającej buildw kreatorze po wywołaniu zakończy się niepowodzeniem z wyjątkiem NullPointerException. Jeśli chcesz być flashem, możesz nawet przetestować na zero i zgłosić IllegalStateException lub coś w zamian. I możesz przenieść to do ogólnej klasy podstawowej, gdzie można by go używać we wszystkich konstruktorach.

Tom Anderson
źródło
1
Chciałbym zmienić 2nd linii w build()celu: this.options = new Options();. W ten sposób instancje Opcje byłyby bezpiecznie niezmienne, a konstruktor byłby wielokrotnego użytku w tym samym czasie.
Natix
5

Konstruktor według wzoru Blocha może być wielokrotnie wykorzystywany do generowania obiektów, które są „w większości” takie same. Ponadto niezmienne obiekty (wszystkie pola są ostateczne, a same niezmienne) mają zalety związane z bezpieczeństwem wątków, które twoje zmiany mogą pokonać.

Steven Schlansker
źródło
0

Jeśli Opcje można skutecznie klonować (to znaczy niezależnie od interfejsu Klonowalnego), można użyć wzorca prototypowego - mieć jeden w kreatorze i sklonować go w build ().

Jeśli nie korzystasz z interfejsu Klonowalnego, musisz skopiować każde pole, aby dodać kolejne miejsce, w którym musisz je dodać, więc przynajmniej dla klasy z prostymi polami faktycznie korzystającymi z Klonowalnego byłoby dobrym pomysłem.

użytkownik470365
źródło