Шаблон строителя и большое количество обязательных параметров
На сегодняшний день я использую следующую реализацию шаблона построителя (в отличие от описанной реализации здесь):
public class Widget {
public static class Builder {
public Builder(String name, double price) { ... }
public Widget build() { ... }
public Builder manufacturer(String value) { ... }
public Builder serialNumber(String value) { ... }
public Builder model(String value) { ... }
}
private Widget(Builder builder) { ... }
}
Это хорошо работает для большинства ситуаций, с которыми я столкнулся, когда мне нужно создать сложный объект с различными требуемыми/обязательными и необязательными параметрами. Тем не менее, я в последнее время изо всех сил пытался понять, как шаблон имеет какую-либо пользу, когда все ваши параметры являются обязательными (или, по крайней мере, подавляющее большинство).
Одним из способов обойти это было логическое объединение параметров, передаваемых в их собственные классы, для уменьшения количества параметров, передаваемых конструктору-строителю.
Например, вместо:
Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
.addOptional(opt9)
.build();
сгруппируется следующим образом:
Object1 group1 = new Object1(req1, req2, req3, req4);
Object2 group2 = new Object2(req5, req6);
Widget example2 = new Widget.Builder(group1, group2, req7, req8)
.addOptional(opt9)
.build();
Несмотря на то, что отдельные объекты упрощают вещи совсем немного, это также затрудняет работу, если вы не знакомы с кодом. Одна вещь, которую я рассматривал, заключается в перемещении всех параметров в свои собственные методы addParam(param)
, а затем выполнение проверки по требуемым параметрам в методе build()
.
Какова наилучшая практика и, возможно, лучший подход к этому, который я не рассматривал?
Ответы
Ответ 1
Однако в последнее время я борется за то, чтобы понять, как шаблон имеет какую-либо пользу, когда все ваши параметры являются обязательными (или, по крайней мере, подавляющее большинство).
Свободный шаблон строителя по-прежнему полезен:
1) Его более читаемый - он эффективно разрешает именованные параметры, чтобы вызов был не просто длинным списком неназванных аргументов
2) Его неупорядоченный - это позволяет группировать аргументы вместе в логические группы, либо как часть одного вызова setbuilder, либо просто позволяя вам использовать естественный порядок вызова методов set set builder, которые имеют наибольший смысл этого конкретного конкретизации.
===
Widget example = new Widget.Builder(req1, req2, req3,req4,req5,req6,req7,req8)
.addOptional(opt9)
.build();
сгруппируется следующим образом:
Object1 group1 = new Object1(req1, req2, req3, req4);
Object2 group2 = new Object2(req5, req6);
Widget example2 = new Widget.Builder(group1, group2, req7, req8)
.addOptional(opt9)
.build();
Несмотря на то, что отдельные объекты упрощают вещи совсем немного, это также затрудняет работу, если вы не знакомы с кодом. Одна вещь, которую я рассматривал, заключается в перемещении всех параметров в свои собственные методы addParam (param), а затем выполнение проверки на требуемые параметры в методе build().
Я предпочел бы гибрид, когда это было бы подходящим или естественным. Это не обязательно должно быть в конструкторе или каждый параметр имеет свой собственный метод addParam. Builder дает вам гибкость, чтобы сделать одно, другое, промежуточное или комбинированное:
Widget.Builder builder = new Widget.Builder(Widget.BUTTON);
builder.withWidgetBackingService(url, resource, id);
builder.withWidgetStyle(bgColor, lineWidth, fontStyle);
builder.withMouseover("Not required");
Widget example = builder.build();
Ответ 2
Вы можете использовать Step Builder, если у вас есть много обязательных параметров. Вкратце: вы определяете интерфейс для каждого обязательного параметра, а метод builder возвращает следующий обязательный интерфейс построителя или сам построитель для дополнительных методов. Строитель остается единственным классом, который реализует все интерфейсы.
Языки, такие как Kotlin и Scala, здесь более удобны, так как они предлагают именованные параметры со значениями по умолчанию.
Ответ 3
Одно из преимуществ шаблона Builder, которое я редко (если когда-либо) вижу в рекламе, заключается в том, что его можно также использовать для условной конструирования объекта, например, только если все обязательные параметры верны или доступны другие необходимые ресурсы. В этом отношении они предлагают аналогичные преимущества для статического метода factory.
Ответ 4
В последнее время я борюсь за то, чтобы понять, как шаблон если все ваши параметры обязательны
Шаблон упрощает создание неизменяемых классов и способствует считыванию кода. Рассмотрим класс Person ниже (с помощью обычного конструктора и строителя).
public static class Person {
private static final class Builder {
private int height, weight, age, income, rank;
public Builder setHeight(final int height) { this.height = height; return this; }
public Builder setWeight(final int weight) { this.weight = weight; return this; }
public Builder setAge(final int age) { this.age = age; return this; }
public Builder setIncome(final int income) { this.income = income; return this; }
public Builder setRank(final int rank) { this.rank = rank; return this; }
public Person build() { return new Person(this); }
}
private final int height;
private final int weight;
private final int age;
private final int income;
private final int rank;
public Person(final int height, final int weight, final int age, final int income, final int rank) {
this.height = height; this.weight = weight; this.age = age; this.income = income; this.rank = rank;
}
private Person(final Builder builder) {
height = builder.height; weight = builder.weight; age = builder.age; income = builder.income; rank = builder.rank;
// Perform validation
}
public int getHeight() { return height; }
public int getWeight() { return weight; }
public int getAge() { return age; }
public int getIncome() { return income; }
public int getRank() { return rank; }
}
Какой способ построения легче понять?
final Person p1 = new Person(163, 184, 48, 15000, 23);
final Person p2 = new Person.Builder().setHeight(163).setWeight(184).setAge(48).
setIncome(15000).setRank(23).build();
Одним из способов обойти это было логическое объединение параметры передаются в свои классы
Конечно, это принцип cohesion и должен быть принят независимо от семантики построения объекта.
Ответ 5
Строитель/factory по-прежнему позволяет вам отделить интерфейс от типа реализации (или позволяет подключать адаптер и т.д.), предполагая, что Widget
становится интерфейсом, и у вас есть способ ввести или скрыть new Widget.Builder
.
Если вы не заботитесь о развязке, а ваша реализация - одноразовая, то вы правы: шаблон построителя не намного полезнее обычного конструктора (он по-прежнему помещает свои аргументы в атрибут- per-builder-method style.)
Если вы неоднократно создаете объекты с небольшим изменением в аргументах, тогда это может быть полезно. Вы можете пройти, кеш и т.д. Промежуточный строитель, приобретенный после подключения нескольких атрибутов:
Widget.Builder base = new Widget.Builder(name, price).model("foo").manufacturer("baz");
// ...
Widget w1 = base.serialNumber("bar").build();
Widget w2 = base.serialNumber("baz").build();
Widget w3 = base.serialNumber("quux").build();
Это предполагает, что ваши строители неизменны: построители построителей не устанавливают атрибут и не возвращают this
, а вместо этого возвращают новую копию себя с изменением. Как вы указали выше, объекты параметров - это еще один способ обойти шаблон повторяющихся аргументов. Там вам даже не нужен шаблон построителя: просто передайте объект параметра вашему конструктору реализации.