代码之家  ›  专栏  ›  技术社区  ›  Steve

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

  •  9
  • Steve  · 技术社区  · 15 年前

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

    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 剂量测定法 变成一个 @Inject public void init() 方法。虽然与guice一起使用是透明的,但它需要任何手动构造来确保调用 init() 否则,如果有人忘记了,这个物体就不能完全发挥作用。此外,这也暴露了更多的API,这两个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 回复  |  直到 15 年前
        1
  •  8
  •   Péter Török    15 年前

    有效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
  •  9
  •   Michael Ekstrand    15 年前

    它还严重破坏了遗产。如果在链中调用构造函数来实例化类的子类,则可以调用在子类中被重写的方法,该方法依赖于在子类构造函数运行之前未建立的不变量。

        3
  •  4
  •   Grant Palin Bob King    15 年前

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

        4
  •  0
  •   Shawn    15 年前

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

    编辑:哇,另一个帖子也提出了同样的问题。看起来你可以让Java中的构造函数是私有的。在vb.net中不能这样做(不确定C)…很酷…

        5
  •  0
  •   SyntaxT3rr0r    15 年前

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

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

    您可以在名为 “有效的Java重载:这次是真正的” ,例如这里:

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