Действительно ли это - хорошая или плохая практика для вызова методов экземпляра от конструктора Java?

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

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

Таким образом, что предпочтительный путь состоит в том, чтобы иметь дело с этим видом ситуации?

9
задан SyntaxT3rr0r 24 March 2010 в 22:50
поделиться

5 ответов

Джош Блох в книге Effective Java рекомендует использовать метод static factory, хотя я не могу найти никаких аргументов для подобных случаев. Однако в Java Concurrency in Practice есть похожий случай, специально предназначенный для предотвращения утечки ссылки на 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.

8
ответ дан 4 December 2019 в 10:03
поделиться

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

4
ответ дан 4 December 2019 в 10:03
поделиться

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

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

0
ответ дан 4 December 2019 в 10:03
поделиться

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

9
ответ дан 4 December 2019 в 10:03
поделиться

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

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

Вы можете найти слайды Джошуа Блоха о (новом) паттерне Builder в слайдовой презентации под названием "Effective Java Reloaded: This Time It's for Real", например, здесь:

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

0
ответ дан 4 December 2019 в 10:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: