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

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

Спасибо, D

Ответы [ 18 ]

31 голосов
/ 27 октября 2009

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

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

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

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

27 голосов
/ 27 октября 2009

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

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

20 голосов
/ 24 июля 2010

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

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

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

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

16 голосов
/ 27 октября 2009

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

ОП был обновлен, чтобы отклонить шаблон построителя, но, похоже, он основан на недоразумении. Тот факт, что шаблон 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;
          }
      } 
 }

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

И если по какой-то причине вы не можете этого сделать, у вас все еще может быть конструктор с 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.
     }

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

4 голосов
/ 27 октября 2009

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

Widget widge = new Widget.Builder(). manufacturer("333").serialNumber("54321").build();
4 голосов
/ 27 октября 2009

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

3 голосов
/ 21 июля 2010

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

2 голосов
/ 27 октября 2009

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

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

2 голосов
/ 21 июля 2010

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

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.
1 голос
/ 27 июля 2010

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

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