Это хорошая или плохая практика для вызова методов экземпляра из Java-конструктора? - PullRequest
9 голосов
/ 25 марта 2010

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

final class MyClass {
  private final Dependency dependency;
  @Inject public MyClass(Dependency dependency) {
    this.dependency = dependency;
    dependency.addHandler(new Handler() {
      @Override void handle(int foo) { MyClass.this.doSomething(foo); }
    });
    doSomething(0);
  }
  private void doSomething(int foo) { dependency.doSomethingElse(foo+1); }
}

Как видите, конструктор делает 3 вещи, включая вызов метода экземпляра. Мне сказали, что вызов методов экземпляра из конструктора небезопасен, потому что он обходит проверки компилятором неинициализированных членов. То есть Я мог бы позвонить doSomething(0) до установки this.dependency, что скомпилировало бы, но не работало. Каков наилучший способ рефакторинга?

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

  2. Переместите addHandler и doSomething в метод @Inject public void init(). В то время как использование с Guice будет прозрачным, требуется, чтобы любая ручная конструкция вызывала init(), иначе объект не будет полностью функциональным, если кто-то забудет. Кроме того, это раскрывает больше API, оба из которых кажутся плохими идеями.

  3. Оберните вложенный класс, чтобы сохранить зависимость, чтобы убедиться, что он ведет себя правильно, без предоставления дополнительного API:

    class DependencyManager {
      private final Dependency dependency;
      public DependecyManager(Dependency dependency) { ... }
      public doSomething(int foo) { ... }
    }
    @Inject public MyClass(Dependency dependency) {
      DependencyManager manager = new DependencyManager(dependency);
      manager.doSomething(0);
    }
    Это извлекает методы экземпляра из всех конструкторов, но создает дополнительный слой классов, и когда у меня уже были внутренние и анонимные классы (например, этот обработчик), это может сбить с толку - когда я попробовал это, мне сказали переместить DependencyManager отдельный файл, который также неприятен, потому что теперь это несколько файлов, чтобы сделать одну вещь.

Так какой же предпочтительный способ справиться с подобной ситуацией?

Ответы [ 5 ]

9 голосов
/ 25 марта 2010

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

8 голосов
/ 25 марта 2010

Джош Блох в Effective Java рекомендует использовать метод статической фабрики, хотя я не могу найти аргументов для подобных случаев. Однако есть аналогичный случай в Java Concurrency на практике , специально предназначенный для предотвращения утечки ссылки на this из конструктора. Применительно к этому случаю это будет выглядеть так:

final class MyClass {
  private final Dependency dependency;

  private MyClass(Dependency dependency) {
    this.dependency = dependency;
  }

  public static createInstance(Dependency dependency) {
    MyClass instance = new MyClass(dependency);
    dependency.addHandler(new Handler() {
      @Override void handle(int foo) { instance.doSomething(foo); }
    });
    instance.doSomething(0);
    return instance;
  }
  ...
}

Однако это может не сработать с используемой вами аннотацией DI.

4 голосов
/ 25 марта 2010

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

0 голосов
/ 25 марта 2010

Вы можете использовать статический метод, который принимает зависимость, конструирует и возвращает новый экземпляр, а также отмечает конструктор Friend. Я не уверен, что Friend существует в Java (хотя он защищен). Это может быть не лучшим способом. Вы также можете использовать другой класс, который является Фабрикой для создания MyClass.

Edit: Wow другой опубликовал только что предложил ту же самую вещь. Похоже, вы можете сделать конструкторы частными в Java. Вы не можете сделать это в VB.NET (не уверен насчет C #) ... очень круто ...

0 голосов
/ 25 марта 2010

Да, это на самом деле незаконно, Оно даже не должно компилироваться (но я верю, что это так)

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

Вы можете найти слайды Джошуа Блоха в (новом) шаблоне Builder в презентации слайдов под названием «Эффективная перезагрузка Java: на этот раз это реально» , например, здесь:

http://docs.huihoo.com/javaone/2007/java-se/

...