Конструктор Java с большими аргументами или подход получения / установки Java bean-компонента

Я не могу решить, какой подход лучше для создания объектов с большим количеством полей (10+) (все обязательные), чем конструктор подход геттера / сеттера. Конструктор, по крайней мере, вы обеспечиваете, чтобы все поля были установлены. Java Beans легче увидеть, какие переменные устанавливаются вместо огромного списка. Шаблон компоновщика здесь НЕ кажется подходящим, поскольку все поля являются обязательными, и компоновщик требует, чтобы вы поместили все обязательные параметры в конструктор компоновщика.

Спасибо д

18 ответов

Лучшим подходом (imho) является использование какого-либо компоновщика:

MyClass a = new MyClassBuilder().blah("blah").foo("foo").doStuff().toMyClass();

где MyClass все еще неизменен, но имеет гораздо более удобочитаемое создание, чем конструктор с 10 аргументами.

Это также называется свободным интерфейсом. Джош Блох ссылается на это в Effective Java.

Моя первая мысль - проверить правильность вашей модели инкапсуляции. Наличие более 10 обязательных полей звучит довольно много, и, возможно, имеет смысл иметь более мелкозернистые компоненты в этом сценарии?

Связаны ли некоторые из этих полей / параметров? Могут ли они быть объединены в объекты, которые имеют смысл (например, x-coordinate а также y-coordinate объединены в Point объект и т. д.)

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

"Чистый код " Роберта Мартина идет еще дальше: он рекомендует 1 или 2 аргумента, а 3 уже считается "запахом кода". Лично я думаю, что Чистый код местами немного экстремален, но в целом он дает хорошие аргументы.

"Целая куча параметров" (как бы много это ни было) говорит о дизайне "кухонной раковины" с множеством запоздалых мыслей и небольшой структурой. Это также делает обслуживание более сложным и подверженным ошибкам; по крайней мере, это затрудняет чтение кода.

Все это заставляет задуматься о сокращении количества параметров. Другие ответы предложили некоторые практические предложения.

Я бы порекомендовал вам рассмотреть шаблон строителя в таком случае. Вы гарантированно получите действительный объект, без огромного списка параметров.

ОП был обновлен, чтобы отклонить шаблон построителя, но, похоже, он основан на недоразумении. Тот факт, что шаблон Builder существует, не устраняет принудительное применение всех параметров.

Рассмотрим следующий объект:

 public class SomeImmutableObject {
      private String requiredParam1;
      private String requiredParam2;
      //etc.

      private SomeImmutableObject() { //cannot be instantiated outside the class }

      public static class Builder {
          private SomeImmutableObject instance;
          public Builder() { instance = new SomeImmutableObject();
          public Builder setParameter1(String value) {
               instance.requiredParam1 = value;
               return this;
          }
          //etc for each parameter.

          public SomeImmutableObject build() {
             if (instance.requiredParam1 == null || instance.requiredParam2 == null /*etc*/)
                throw new IllegalStateException("All required parameters were not supplied.");
             return instance;
          }
      } 
 }

Обратите внимание, что вы можете сделать в основном то же самое, сделав пакет fields закрытым и поместив компоновщик в тот же пакет.

И если по какой-то причине вы не можете этого сделать, у вас все еще может быть конструктор с 10 параметрами, и тогда Builder будет единственным, что вызывает этот конструктор, чтобы его было проще использовать API.

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

Изменить: ОП добавил комментарий (довольно давно, но я только что получил возражение по этому вопросу, поэтому я только видел его сейчас) с интересным вопросом: как вы проверяете примитив в более поздний момент времени?

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

     private Double doubleForPrimitive;

     public Builder setDouble(double d) {
         doubleForPrimitive = d;
     }

     public SomeImmutableObject build() {
         if(doubleForPrimitive != null) {
               instance.doubleParam = doubleForPrimitive;
         } else {
                throw new IllegalArgumentExcepion("The parameter double was not provided");
         }
         //etc.
     }

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

Эти две модели полезны для размышления о сценарии такого рода:

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

Widget widge = new Widget.Builder(). manufacturer("333").serialNumber("54321").build();

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

Я бы реализовал шаблон построителя так:

package so1632058;

public class MyClass {
  private final String param1;
  private final String param2;

  MyClass(Builder builder) {
    this.param1 = builder.param1;
    this.param2 = builder.param2;
  }

  public String getParam1() {
    return param1;
  }

  public String getParam2() {
    return param2;
  }

  @SuppressWarnings("hiding")
  public static final class Builder {
    String param1;
    String param2;

    public Builder param1(String param1) {
      this.param1 = param1;
      return this;
    }

    public Builder param2(String param2) {
      this.param2 = param2;
      return this;
    }

    public MyClass toMyClass() {
      return new MyClass(this);
    }
  }
}

И затем используйте следующий код, чтобы использовать его:

package so1632058;

public class Main {

  public static void main(String[] args) {
    MyClass.Builder builder = new MyClass.Builder();
    builder.param1("p1").param2("p2");
    MyClass instance = builder.toMyClass();
    instance.toString();
  }

}

Некоторые заметки:

  • Нет методов с большим количеством параметров.
  • Дополнительная проверка может быть выполнена в MyClass конструктор.
  • Я сделал конструктор видимости для всего пакета, чтобы избежать предупреждения "синтетического доступа".
  • То же самое для полей экземпляра строителя.
  • Единственный способ построить экземпляр MyClass через Builder,

ИМХО, вы должны передать все, что нужно для того, чтобы объект был действительным в соответствии с вашей бизнес-логикой в ​​конструкторе.

Если список аргументов длинный, вы можете создать объект, содержащий аргументы, и передать его.

IMO конструкторы не формируют хороший API при создании объектов, особенно когда количество аргументов велико и они одного типа.

new Person(String, String, String, String); // what the?? this is what may 
                                            // show up in IDEs

где это на самом деле означает лицо (имя, фамилия, имя экрана, пароль, (например, ради)

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

Другой подход, который вы можете использовать, - рассмотреть возможность использования внутреннего DSL. Но это имеет смысл, только если вы создаете такие объекты, как конфигурации, запросы и т. Д. Посмотрите, имеет ли смысл иметь внутренний DSL в вашем случае.

У нас была похожая проблема в нашем проекте. Нам пришлось получить определенные значения из домашнего шлюза (наш продукт). Он поддерживал запрос-ответ, основанный на запросе XML-протокол через http. Но создание объекта Request для отправки в пользовательском интерфейсе было утомительным, с настройкой объектов Request с соответствующими параметрами и фильтрами и т. Д.

Изначально наш объект запроса выглядел так:

Request r = new Request("SET");
r.setAction("add"); // modify, add, delete
r.setModuleName("NPWIFI"):
r.addParameter(new Param("wifiAclMac", "aa:bb:cc:dd:ee:ff"));
r.addParameter(new Param("wifiCommit", "commit"));
r.setActionParam("wifiAclRowStatus")
r.addFilter(new Filter(Type.EQUALS, "wifiInterface", "wl0"));
r.addFilter(new Filter(Type.EQUALS, "wifiAclMac", "yourgateway"));

Resonse r = gwSession.sendRequest(r);

Таким образом, мы изменили его на внутренний DSL, который был похож на SQL, но только программный

Query q = DQL.add("wifiAclMac", "wifiCommit").into("NPWIFI").values
            ("aa:bb:cc:dd:ee:ff", "commit").withAp("wifiAclRowStatus")
            .where("wifiInterface").is("wl0")
            .and("wifiAclMac").is("aa:bb:cc:dd:ee:ff").end();

Объект DQL "построитель запросов" выполнил всю сборку с проверкой, а также оказался очень удобным в использовании.

Построители и DSL являются элегантным и мощным средством создания и создания объектов, но посмотрите, что имеет смысл в вашем случае.

Если все параметры являются обязательными, то я не вижу причин, почему бы не использовать конструктор. Однако, если это не так, то использование компоновщика кажется лучшим подходом.
Полагаться только на сеттеры - это, на мой взгляд, худшее решение, так как нечего требовать, чтобы все обязательные свойства были установлены. Конечно, если вы используете bean-компонент Spring Framework или другое подобное решение, то Java-бины прекрасно работают, так как после инициализации вы можете проверить, что все установлено.

На это довольно сложно ответить абстрактно. Что действительно нужно сделать, так это посмотреть на эти десять параметров и посмотреть, что они из себя представляют. Я вижу это как ключевые вопросы:

  • Могут ли некоторые из них быть объединены в объекты "уровня" более высокого уровня? Например, переменные X и Y могут быть объединены в Point. У нас было много подобных ситуаций в программе маршрутизации грузов, где все поля были смоделированы как примитивные строки. Введение нескольких концепций более высокого уровня действительно помогло сделать его читабельным.
  • Можно ли использовать некоторые параметры по умолчанию для определенных значений?
  • Действительно ли они все независимые, ортогональные понятия? Я работал на многих системах и никогда не видел, чтобы это было правдой. Если нет, то нужно подумать над этим.

Это вопрос дизайна без сомнения. Вы должны взвесить удобочитаемость с легкостью. Конструктор из десяти аргументов проще, но может быть, а может и не быть более читабельным / поддерживаемым. Он также имеет меньше вызовов методов для включения и выключения стека вызовов. Задание десяти различных значений через сеттеры немного более читабельно и явно. Это не обязательно "проще" (хотя вы можете спорить обоими способами) и добавляет больше вызовов методов для включения и выключения стека вызовов.

Но вот еще несколько моментов для размышления. Даже с помощью конструктора с десятью аргументами вы можете попросить программиста передать значение null, пробел, false или ноль (в зависимости от объекта или примитива), что может быть, а может и не совпадать с вашим намерением. Единственный способ контролировать это - потенциально вызвать исключение в конструкторе. Это то, что вы действительно хотите сделать? Это поведение, которое вы ожидаете?

Конечно, устанавливая каждую переменную отдельно через сеттеры, вы, возможно, не сможете узнать, когда вы закончите или не закончили создание объекта. Вот где шаблон Построителя, обсужденный выше, мог бы помочь. Сделайте так, чтобы он создал объект, установил значения, а затем подтвердил, что все установлены. Если чего-то не хватает, потому что программист решил не передавать что-то, то вы защищены. Ваш класс не должен делать больше, чем положено. (В конце концов, важно подумать о том, кто может использовать ваш класс когда-нибудь. Они могут не понимать вашего намерения, несмотря на все великие Javadoc в мире.)

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

Опять же, вы должны решить эти вещи самостоятельно. Но, пожалуй, наиболее важным из всего этого является рассмотрение удобочитаемости, а не эффективности, чтобы сделать код более понятным, и создание API и поведения, которые программисты, которые придут после вас, не смогут использовать. Предвидеть защиту от злоупотреблений в вашем дизайне, что бы вы ни использовали.

Действительно зависит от конкретного класса. Должно ли оно быть неизменным? Это простой объект значения без какого-либо поведения? Собираетесь ли вы сопоставить этот объект значения параметрам веб-службы или реляционной базе данных? Собираетесь ли вы его сериализовать? (Некоторым из этих вещей нужен конструктор по умолчанию). Можете ли вы рассказать немного больше об объекте?

Существуют ли варианты класса, которые могут принимать меньше аргументов, или только один, и он имеет десять свойств?

Объект должен быть неизменным?

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

Могут ли ваши поля быть объединены в промежуточный объект? Например, если вы передаете 10 полей, которые описывают человека, то создайте объект PersonInfo для передачи этих данных. Я лично предпочитаю передавать все необходимые поля при создании объекта. Таким образом, вы не получите полуобожженный объект, который неизбежно будет использован и оскорблен.

Если все параметры действительно требуются, и вы не используете Spring для создания экземпляра bean-компонента, я бы определенно использовал конструктор с 10 параметрами. Совершенно очевидно, что все параметры действительно необходимы.

Если вы используете Spring (возможно, иногда) для создания bean-компонента или вам не очень нравится иметь много временных переменных в методе, который создает этот bean-компонент, вы также можете использовать методы getter и setter. Если вы также реализуете метод validate для проверки правильности объекта, у вас не должно возникнуть проблем.

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

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

Для чего это стоит; если у вас есть 10 обязательных аргументов, и вы не можете сгруппировать их логически, возможно, вы просто смешали много разных понятий в одном классе; в этом случае, возможно, было бы еще лучше тщательно и тщательно проанализировать ваш дизайн и немного изменить его.

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

Я бы передал интерфейс к вашему конструктору, который вы могли бы изменить, не нарушая код, или использовать подход Javabeans и не иметь конструктора arg.

Другие вопросы по тегам