Расскажите об этой статье доктора Доббса и, в частности, о шаблоне Builder, как нам поступить в случае создания подкласса от Builder? Если взять урезанную версию примера, в котором мы хотим создать подкласс для добавления маркировки ГМО, наивная реализация будет выглядеть так:
public class NutritionFacts {
private final int calories;
public static class Builder {
private int calories = 0;
public Builder() {}
public Builder calories(int val) { calories = val; return this; }
public NutritionFacts build() { return new NutritionFacts(this); }
}
protected NutritionFacts(Builder builder) {
calories = builder.calories;
}
}
Подкласс:
public class GMOFacts extends NutritionFacts {
private final boolean hasGMO;
public static class Builder extends NutritionFacts.Builder {
private boolean hasGMO = false;
public Builder() {}
public Builder GMO(boolean val) { hasGMO = val; return this; }
public GMOFacts build() { return new GMOFacts(this); }
}
protected GMOFacts(Builder builder) {
super(builder);
hasGMO = builder.hasGMO;
}
}
Теперь мы можем написать такой код:
GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);
Но, если мы сделаем неправильный порядок, все не удастся:
GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);
Проблема, конечно, в том, что NutritionFacts.Builder
возвращает a NutritionFacts.Builder
, а не a GMOFacts.Builder
, так как же нам решить эту проблему, или есть лучший шаблон для использования?
Примечание: этот ответ на аналогичный вопрос предлагает классы, которые у меня есть выше; мой вопрос касается проблемы обеспечения правильного порядка вызовов строителя.
java
design-patterns
Кен Я.Н.
источник
источник
build()
на выходеb.GMO(true).calories(100)
?Ответы:
Вы можете решить эту проблему с помощью дженериков. Я думаю, это называется «любопытно повторяющиеся общие шаблоны».
Сделайте тип возвращаемого значения методов построителя базового класса универсальным аргументом.
Теперь создайте экземпляр базового построителя с построителем производного класса в качестве универсального аргумента.
источник
implements
вместоextends
, или (в) все выбросить. У меня странная ошибка компиляции, гдеleafBuilder.leaf().leaf()
иleafBuilder.mid().leaf()
все в порядке, ноleafBuilder.leaf().mid().leaf()
не удается ...return (T) this;
приводит кunchecked or unsafe operations
предупреждению. Этого невозможно избежать, правда?unchecked cast
предупреждение, см. Предлагаемое ниже решение среди других ответов: stackoverflow.com/a/34741836/3114959Builder<T extends Builder>
на самом деле это rawtype - так и должно бытьBuilder<T extends Builder<T>>
.Builder
forGMOFacts
также должен быть общимBuilder<B extends Builder<B>> extends NutritionFacts.Builder<Builder>
- и этот шаблон может продолжаться на столько уровней, сколько потребуется. Если вы объявите неуниверсальный конструктор, вы не сможете расширить шаблон.Просто для записи, чтобы избавиться от
для
return (T) this;
утверждения, о котором говорят @dimadima и @Thomas N., в некоторых случаях применяется следующее решение.Создайте
abstract
конструктор, который объявляет универсальный тип (T extends Builder
в данном случае) и объявляетprotected abstract T getThis()
абстрактный метод следующим образом:См. Http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 для получения дополнительных сведений.
источник
build()
метод возвращает NutrutionFacts здесь?public GMOFacts build() { return new GMOFacts(this); }
BuilderC extends BuilderB
аBuilderB extends BuilderA
когдаBuilderB
нетabstract
Основываясь на сообщении в блоге , этот подход требует, чтобы все классы, не являющиеся конечными, были абстрактными, а все классы листьев были окончательными.
Затем у вас есть промежуточный класс, который расширяет этот класс и его конструктор, и еще столько, сколько вам нужно:
И, наконец, конкретный листовой класс, который может вызывать все методы построителя для любого из своих родителей в любом порядке:
Затем вы можете вызывать методы в любом порядке из любого класса в иерархии:
источник
B
, он всегда оказывается базовым классом.<T extends SomeClass, B extends SomeClass.Builder<T,B>> extends SomeClassParent.Builder<T,B>
шаблону, что и промежуточный класс SecondLevel, вместо этого он объявляет определенные типы. Вы не можете создать класс, пока не дойдете до листа, используя определенные типы, но как только вы это сделаете, вы не сможете его расширить, потому что вы используете определенные типы и отказались от шаблона Curious Recurring Template Pattern. Эта ссылка может помочь: angelikalanger.com/GenericsFAQ/FAQSections/…Вы также можете переопределить
calories()
метод и позволить ему вернуть расширяющийся построитель. Это компилируется, потому что Java поддерживает ковариантные возвращаемые типы .источник
Существует также другой способ создания классов по
Builder
шаблону, который соответствует принципу «Предпочитать композицию наследованию».Определите интерфейс, который
Builder
наследует родительский класс :Реализация
NutritionFacts
почти такая же (за исключениемBuilder
реализации интерфейса FactsBuilder):Класс
Builder
дочернего класса должен расширять тот же интерфейс (за исключением другой общей реализации):Обратите внимание,
NutritionFacts.Builder
это поле внутриGMOFacts.Builder
(называетсяbaseBuilder
). Метод, реализованный изFactsBuilder
интерфейса, вызываетbaseBuilder
одноименный метод:Также произошли большие изменения в конструкторе
GMOFacts(Builder builder)
. Первый вызов в конструкторе конструктора родительского класса должен передать соответствующиеNutritionFacts.Builder
:Полная реализация
GMOFacts
класса:источник
Полный трехуровневый пример наследования множественных построителей будет выглядеть так :
(Для версии с конструктором копирования для построителя см. Второй пример ниже)
Первый уровень - родительский (потенциально абстрактный)
Второй уровень
Третий уровень
И пример использования
Немного более длинная версия с конструктором копирования для конструктора:
Первый уровень - родительский (потенциально абстрактный)
Второй уровень
Третий уровень
И пример использования
источник
Если вы не хотите высовывать глаза на угловую скобу или три, или, возможно, не чувствуете, что вы ... ммм ... я имею в виду ... кашля ... остальная часть вашей команды быстро поймет с любопытством повторяющийся шаблон дженериков, вы можете сделать это:
поддерживается
и родительский тип:
Ключевые моменты:
РЕДАКТИРОВАТЬ:
Я нашел способ обойти создание ложного объекта. Сначала добавьте это к каждому строителю:
Затем в конструкторе для каждого строителя:
Стоимость - это дополнительный файл класса для
new Object(){}
анонимного внутреннего класса.источник
Вы могли бы создать статический фабричный метод в каждом из ваших классов:
Затем этот статический фабричный метод вернет соответствующий построитель. Вы можете
GMOFacts.Builder
расширить aNutritionFacts.Builder
, это не проблема. Проблема здесь будет в видимости ...источник
Следующий вклад IEEE Refined Fluent Builder на Java дает исчерпывающее решение проблемы.
Он разбивает исходный вопрос на две подзадачи: недостаточность наследования и квазиинвариантность и показывает, как решение этих двух подзадач открывается для поддержки наследования с повторным использованием кода в классическом шаблоне построителя в Java.
источник
Я создал родительский абстрактный общий класс построителя, который принимает два параметра формального типа. Первый - это тип объекта, возвращаемого функцией build (), второй - тип, возвращаемый каждым дополнительным установщиком параметров. Ниже приведены родительский и дочерний классы для наглядности:
Этот удовлетворил мои потребности.
источник