从java构造函数调用实例方法是好的还是坏的做法?

Ste*_*eve 9 java constructor guice

我可以通过几种不同的方式初始化复杂对象(注入依赖项和注入成员所需的设置),看起来都很合理,但各有利弊.我举一个具体的例子:

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); }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,构造函数执行了3项操作,包括调用实例方法.我被告知从构造函数中调用实例方法是不安全的,因为它绕过了编译器对未初始化成员的检查.即我可以doSomething(0)在设置之前调用this.dependency,这将编译但不起作用.重构这个的最佳方法是什么?

  1. make doSomethingstatic并明确传入依赖项?在我的实际情况中,我有三个实例方法和三个成员字段都相互依赖,所以这似乎是很多额外的样板,使所有这三个静态.

  2. 移动addHandlerdoSomething进入@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到一个单独的文件,这也是令人反感的,因为它现在是多个文件来做一件事.

那么处理这种情况的首选方法是什么?

Mic*_*and 9

它也与继承严重混淆.如果在链中调用构造函数来实例化类的子类,则可以调用在子类中重写的方法,并依赖于在子类构造函数运行之前未建立的不变量.


Pét*_*rök 8

Effective Java中的Josh Bloch建议使用静态工厂方法,尽管我找不到像这样的情况的任何参数.但是,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;
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

但是,这可能不适用于您使用的DI注释.