10

有几种不同的方法可以初始化复杂对象(使用注入的依赖项和所需的注入成员设置),看起来都很合理,但各有优缺点。我举一个具体的例子:

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. addHandlerand移动doSomething到一个@Inject public void init()方法中。虽然与 Guice 一起使用是透明的,但它需要任何手动构造以确保调用init(),否则如果有人忘记,该对象将无法完全发挥作用。此外,这暴露了更多的 API,这两者似乎都是坏主意。

  3. 包装一个嵌套类以保持依赖关系,以确保它在不暴露额外 API 的情况下正常运行:

    类依赖管理器{
      私有最终依赖依赖;
      公共 DependecyManager(依赖依赖) { ... }
      公共 doSomething(int foo) { ... }
    }
    @Inject public MyClass(依赖依赖){
      DependencyManager manager = new DependencyManager(dependency);
      manager.doSomething(0);
    }
    这会将实例方法从所有构造函数中提取出来,但会生成一个额外的类层,当我已经有内部和匿名类(例如那个处理程序)时,它可能会变得混乱 - 当我尝试这个时,我被告知将其移动DependencyManager到一个单独的文件,这也令人反感,因为它现在是多个文件来做一件事情。

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

4

5 回答 5

9

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 注释。

于 2010-03-24T21:56:44.113 回答
9

它也与继承严重混淆。如果在链中调用构造函数来实例化类的子类,则可以调用在子类中被覆盖的方法,并且依赖于直到子类构造函数运行后才建立的不变量。

于 2010-03-24T22:01:44.010 回答
4

您需要小心使用构造函数中的实例方法,因为该类尚未完全构造。如果被调用的方法使用了一个还没有被初始化的成员,那么不好的事情就会发生。

于 2010-03-24T22:23:13.390 回答
0

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

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

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

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

于 2010-03-24T21:53:51.467 回答
0

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

编辑:哇,另一个帖子刚刚提出了同样的问题。看起来您可以在 Java 中将构造函数设为私有。你不能在 VB.NET 中做到这一点(不确定 C#)......非常酷......

于 2010-03-24T21:58:11.770 回答