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

时间:2010-03-24 21:50:04

标签: 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); }
}

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

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

  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);
    }
    这会从所有构造函数中提取实例方法,但会生成一个额外的类层,当我已经有内部和匿名类(例如该处理程序)时,它会变得混乱 - 当我尝试这个时,我被告知要移动{{1}到一个单独的文件,这也是令人反感的,因为它现在是多个文件来做一件事。

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

5 个答案:

答案 0 :(得分:9)

遗传也很糟糕。如果在链中调用构造函数来实例化类的子类,则可以调用在子类中重写的方法,并依赖于在子类构造函数运行之前未建立的不变量。

答案 1 :(得分: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;
  }
  ...
}

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

答案 2 :(得分:4)

您需要注意在构造函数中使用实例方法,因为类尚未完全构造。如果被调用的方法使用的是尚未初始化的成员,那么就会发生坏事。

答案 3 :(得分:0)

是的,它实际上是非法的,它甚至不应该编译(但我相信它确实存在)

考虑构建器模式(并且倾向于不可变,在构建器模式术语中意味着您不能两次调用任何setter并且在对象被“使用”之后无法调用任何setter - 在此处调用setter point应该可能抛出运行时异常。)

你可以在一个名为“Effective Java Reloaded:This Time It for Real”的幻灯片演示文稿中找到Joshua Bloch关于(新)Builder模式的幻灯片,例如:

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

答案 4 :(得分:0)

您可以使用一个静态方法来获取依赖关系并构造并返回一个新实例,并标记构造函数Friend。我不确定朋友是否存在于java中(是否受到包保护。)这可能不是最好的方法。您还可以使用另一个作为Factory的类来创建MyClass。

编辑:哇另一个帖子刚建议同样的事情。看起来你可以在Java中使构造函数成为私有的。你不能在VB.NET中做到这一点(不确定C#)......非常酷......