2010-03-24 27 views
9

我有几种不同的方法可以初始化复杂的对象(注入依赖关系和注入成员所需的设置),都显得合理,但有各种优点和缺点。我给一个具体的例子:从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样东西,包括调用一个实例方法。我被告知从构造函数中调用实例方法是不安全的,因为它绕过了编译器对未初始化成员的检查。即在设置this.dependency之前,我可以调用doSomething(0),这将会编译但不起作用。什么是重构这个最好的方法?

  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); 
    }
    这会将实例方法的所有构造函数,但产生的阶级额外的一层,当我已经有了内,匿名类(例如那个处理程序)它会变得令人困惑 - 当我尝试这个时,我被告知将DependencyManager移动到一个单独的文件中,这也是令人厌恶的,因为它现在是多个文件来完成一件事情。

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

+1

@Steve:我刚刚删除了第一个“pre”标签,以便代码显示使用颜色编码的语法:) – SyntaxT3rr0r 2010-03-24 22:53:21

+0

酷,不知道它的工作方式。 – Steve 2010-03-25 03:35:49

回答

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

+0

我使用的是Guice(实际上是杜松子酒) - 它不直接*支持静态工厂,但它也不阻止它们 - 我只需要在'GinModule'中添加一个额外的工厂方法副本。另一种选择是使'Provider '嵌套类并将'MyClass'注释为'@ProvidedBy(MyClass.MyProvider.class)''。我一直在试图避免静态工厂方法,因为静态方法可能会在测试中造成很多麻烦,但是我意识到构造函数实际上是一样的。 – Steve 2010-03-25 03:40:26

0

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

考虑,而不是建造者模式(和朝向不变瘦这在生成器模式而言意味着你不能打电话任何setter两次,并且在对象被“使用”之后不能调用任何setter - 在那个时候调用setter可能会抛出一个运行时异常)。

你可以找到约书亚·布洛克的幻灯片上称为“有效的Java重装上阵:这一次它的真正的”幻灯片演示(新)Builder模式,例如这里:

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

+1

它编译的事实使它不是“非法”的;不良做法/“不这样做”与非法之间有区别 – 2010-03-25 02:12:43

+0

你说得对,我可能用了错误的词,但任何体面的皮棉程序都会显示这至少是一个警告,可能是一个错误 - 我我不确定他们为什么允许它,但是我仍然不确定他们为什么允许Java中的公共成员......这都是一个谜。 – 2010-03-25 16:50:18

0

你可以使用一个静态方法来获取依赖关系并构造并返回一个新实例,并标记构造函数Friend。我不确定朋友是否存在于Java中(尽管它受软件包保护)。但这可能不是最好的方法。你也可以使用另一个类来创建MyClass。

编辑:哇另一个发布只是建议这个相同的确切的事情。看起来你可以使构造函数在Java中是私有的。你不能在VB.NET中做到这一点(不知道关于C#)...非常酷...

9

它也与继承错误地混淆。如果在链中调用构造函数来实例化类的子类,那么可以调用在子类中被覆盖的方法,并依赖一个在子类构造函数运行之前不会建立的不变量。

+0

好点 - 我没想过!在这种情况下,它不是问题,因为'MyClass'是'final',但是要记住。 – Steve 2010-03-24 22:08:16

4

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

相关问题