Управление конструкторами с множеством параметров в Java

106

В некоторых наших проектах есть иерархия классов, которая добавляет дополнительные параметры по мере продвижения по цепочке. Внизу некоторые классы могут иметь до 30 параметров, 28 из которых просто передаются в суперконструктор.

Я признаю, что использование автоматического DI через что-то вроде Guice было бы неплохо, но по некоторым техническим причинам эти конкретные проекты ограничены Java.

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

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

Стив Армстронг
источник

Ответы:

266

Шаблон проектирования Builder может помочь. Рассмотрим следующий пример

public class StudentBuilder
{
    private String _name;
    private int _age = 14;      // this has a default
    private String _motto = ""; // most students don't have one

    public StudentBuilder() { }

    public Student buildStudent()
    {
        return new Student(_name, _age, _motto);
    }

    public StudentBuilder name(String _name)
    {
        this._name = _name;
        return this;
    }

    public StudentBuilder age(int _age)
    {
        this._age = _age;
        return this;
    }

    public StudentBuilder motto(String _motto)
    {
        this._motto = _motto;
        return this;
    }
}

Это позволяет нам писать код вроде

Student s1 = new StudentBuilder().name("Eli").buildStudent();
Student s2 = new StudentBuilder()
                 .name("Spicoli")
                 .age(16)
                 .motto("Aloha, Mr Hand")
                 .buildStudent();

Если мы опустим обязательное поле (предположительно, требуется имя), тогда мы можем заставить конструктор Student генерировать исключение. И это позволяет нам иметь аргументы по умолчанию / необязательные аргументы без необходимости отслеживать какой-либо порядок аргументов, поскольку любой порядок этих вызовов будет работать одинаково хорошо.

Эли Кортрайт
источник
10
Конечно, со статическим импортом вам вообще никогда не придется «видеть» этих «строителей». Например, у вас могут быть статические методы name (String name), которые возвращают конструктор, и Student (StudentBuilder), который возвращает ученика. Следовательно, Студент (имя («Джо»). Возраст (15). Девиз («Я обмочился»));
oxbow_lakes
2
@oxbow_lakes: в вашем примере какой класс имеет имя статического метода (имя строки)?
user443854
Технически говоря, можно использовать класс Student для создания нового ученика. Я добавил методы внутри класса Student, и все работало нормально. Таким образом, мне не нужно было иметь еще один класс строителя. Я не уверен, что это желательно. Есть ли причина использовать другой класс (StudentBuilder) для его создания?
WVrock
1
@WVrock: Это зависит от вашей реализации. Как я сказал в своем ответе, выполнение этого с самим классом ученика потенциально может оставить класс в полуинициализированном состоянии, например, если у вас есть обязательное поле, которое еще не было инициализировано.
Eli Courtwright
@EliCourtwright Я думаю, это касается предпочтений / дизайна кода. Вместо того, чтобы заставить конструктор генерировать исключение, я заставил buildStudent()метод генерировать исключение.
WVrock
25

Можете ли вы инкапсулировать связанные параметры внутри объекта?

например, если параметры похожи на


MyClass(String house, String street, String town, String postcode, String country, int foo, double bar) {
  super(String house, String street, String town, String postcode, String country);
  this.foo = foo;
  this.bar = bar;

тогда вы могли бы вместо этого иметь:


MyClass(Address homeAddress, int foo, double bar) {
  super(homeAddress);
  this.foo = foo;
  this.bar = bar;
}

JeeBee
источник
8

Что ж, использование шаблона построителя может быть одним из решений.

Но как только вы дойдете до 20-30 параметров, я бы предположил, что между параметрами существует высокая взаимосвязь. Поэтому (как предлагается) заключить их в логически разумные объекты данных, вероятно, имеет наибольший смысл. Таким образом, объект данных уже может проверить действительность ограничений между параметрами.

Для всех моих прошлых проектов, когда я дошел до того, что у меня слишком много параметров (а это было 8, а не 28!), Я смог очистить код, создав лучшую модель данных.


источник
4

Поскольку вы ограничены Java 1.4, если вы хотите DI, тогда Spring будет очень достойным вариантом. DI полезен только в тех местах, где параметры конструктора являются службами или чем-то, что не меняется во время выполнения.

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

Гумундур Бьярни
источник
Как вы упомянули, параметры в основном являются услугами, поэтому DI - это то, что мне нужно. Я думаю, что шаблон Builder, упомянутый в нескольких других ответах, - это именно то, на что я надеялся.
Стив Армстронг,
4

Лучшее решение - не использовать слишком много параметров в конструкторе. Только параметры, действительно необходимые в конструкторе, - это параметры, необходимые для правильной инициализации объекта. У вас могут быть конструкторы с несколькими параметрами, но также есть конструктор с минимальными параметрами. Дополнительные конструкторы вызывают этот простой конструктор, а затем установщики для установки других параметров. Таким образом, вы можете избежать цепной проблемы с все большим и большим количеством параметров, а также иметь некоторые удобные конструкторы.

Mnementh
источник
2

Я действительно рекомендую использовать Immutables или POJOBuilder при использовании шаблона строителя.

Томас Бьерре
источник
1

Рефакторинг для уменьшения количества параметров и глубины иерархии наследования - это почти все, о чем я могу думать, потому что на самом деле ничто не поможет сохранить правильность 20-с чем-то параметров. Вам просто нужно будет каждый раз звонить, просматривая документацию.

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

Аарон Маенпаа
источник