Улучшения в шаблоне дизайна Джошуа Блоха?

12

Еще в 2007 году я прочитал статью о том, как Джошуа Блохс рассматривает «шаблон строителя» и как его можно изменить, чтобы улучшить чрезмерное использование конструкторов и сеттеров, особенно когда объект имеет большое количество свойств, большинство из которых являются необязательными. Краткое описание этого шаблона проектирования изложено здесь .

Мне понравилась идея, и я использую ее с тех пор. Проблема с этим, хотя он очень чистый и приятный в использовании с точки зрения клиента, его реализация может быть проблемой для задницы! В объекте так много разных мест, где одно свойство является ссылкой, поэтому создание объекта и добавление нового свойства занимает много времени.

Итак ... У меня была идея. Во-первых, пример объекта в стиле Джошуа Блоха:

Джош Блох Стиль:

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

Ответы:

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

Что могло бы предотвратить это - любой вызов метода-установщика в компоновщике после buildвызова завершится с ошибкой NullPointerException. Если вы хотите быть flash, вы можете даже проверить на null и вместо этого выдать IllegalStateException или что-то еще. И вы могли бы перенести это в общий базовый класс, где он мог бы использоваться всеми сборщиками.

Том Андерсон
источник
1
Я бы изменить на 2 - й линии в build()к: this.options = new Options();. Таким образом, экземпляры параметров будут надежно неизменными, а компоновщик будет использоваться повторно.
Natix,
5

Строитель в паттерне Блоха может использоваться много раз для генерации объектов, которые «в основном» одинаковы. Кроме того, неизменяемые объекты (все поля являются окончательными и сами неизменяемыми) имеют преимущества безопасности потоков, которые могут быть нарушены вашими изменениями.

Стивен Шланскер
источник
0

Если бы Options было эффективно клонируемым (я имею в виду, независимо от Cloneable интерфейса), вы могли бы использовать шаблон прототипа - иметь один в builder и клонировать его в build ().

Если вы не используете интерфейс Cloneable, вам придется скопировать каждое поле, чтобы оно добавляло другое место, где вам нужно добавить его, так что по крайней мере для класса с простыми полями, на самом деле использование Cloneable было бы хорошей идеей.

user470365
источник