Шаблон строителя и большое количество обязательных параметров - PullRequest
53 голосов
/ 05 сентября 2011

На сегодняшний день я использую следующую реализацию шаблона построителя (в отличие от реализации, описанной здесь ):

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

Что такое лучшая практика и, возможно, есть лучший подход к этому, который я не рассматривал?

Ответы [ 5 ]

31 голосов
/ 07 марта 2016

Вы можете использовать Step Builder , если у вас много обязательных параметров. Вкратце: вы определяете интерфейс для каждого обязательного параметра, а метод компоновщика возвращает следующий обязательный интерфейс компоновщика или сам компоновщик для необязательных методов. Конструктор остается единственным классом, который реализует все интерфейсы.

Здесь более удобны такие языки, как Kotlin и Scala, поскольку они предлагают именованные параметры со значениями по умолчанию.

27 голосов
/ 05 сентября 2011

Однако в последнее время я изо всех сил пытался понять, насколько полезен шаблон, когда все ваши параметры являются обязательными (или, по крайней мере, подавляющим большинством).

Свободный шаблон строителя все еще полезен:

  1. Это более читабельно - он эффективно позволяет именованные параметры, так что вызов не просто длинный список неназванных аргументов

  2. Его неупорядоченный - это позволяет группировать аргументы в логические группы, либо как часть одного вызова установщика-установщика, либо просто позволяя вам использовать естественный порядок для вызова методов-установщиков, которые в наибольшей степени понимают это. конкретный экземпляр.


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();
3 голосов
/ 12 сентября 2013

Одно из преимуществ шаблона построителя, которое я редко (если вообще когда-либо) вижу в продвижении, заключается в том, что его также можно использовать для условного построения объекта, например, только если все обязательные параметры верны или если доступны другие необходимые ресурсы. В этом отношении они предлагают аналогичные преимущества для статического фабричного метода .

3 голосов
/ 05 сентября 2011

В последнее время я изо всех сил пытался понять, как выгода, когда все ваши параметры обязательны

Шаблон облегчает создание неизменяемых классов и продвигает читаемый код. Рассмотрим класс 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();

Одним из способов обойти это было логически сгруппировать параметры, передаваемые в их собственные классы

Конечно, это принцип сплоченности и должен приниматься независимо от семантики конструкции объекта.

0 голосов
/ 05 сентября 2011

Конструктор / фабрика по-прежнему позволяет вам отделить интерфейс от типа реализации (или позволяет подключить адаптер и т. Д.), Предполагая, что Widget становится интерфейсом, и у вас есть способ внедрить или скрыть new Widget.Builder.

Если вы не заботитесь о разделении, и ваша реализация одноразовая, то вы правы: шаблон построителя не намного полезнее обычного конструктора (он по-прежнему помечает свои аргументы атрибутом- Стиль для каждого строителя.)

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

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, но вместо этого возвращают новую копию себя вместе с изменением. Как вы указали выше, объекты параметров - это еще один способ обойти шаблон повторяющихся аргументов. Там вам даже не нужен шаблон компоновщика: просто передайте объект параметра в конструктор реализации.

...