在另一个中实现Object的最佳方法 - java

时间:2016-11-23 10:40:34

标签: java oop

给定两个类A和B. B包含一个对象A. 在创建B对象的那一刻,我们需要创建它的A对象。 所以问题是:在B的构造函数中或在decalration时刻更好地实现A?让我们看看这个简单的例子:

public Class A{
//Staff
}

- 第一个提案

public Class B{
//staff
A a;
public B(){
a = new A()
}

}

- 第二项提案

public Class B{
//staff
A a = new A();
public B(){
//staff
}
}

那么两种解决方案有什么区别?

5 个答案:

答案 0 :(得分:2)

如果您希望通过构造函数或通过适当的setter设置它,我认为您应该将A的实例传递给B的构造函数。否则你就是强迫两个类紧密耦合。

答案 1 :(得分:1)

我建议您使用第一个,因为您可以向构造函数添加参数。

public Class B {
    A a;
    public B() {
        a = new A();
    }
    public B(A a) {
        this.a = a;
    }
}

答案 2 :(得分:1)

在您的具体示例中,两者没有区别。

通常,如果可以使用第二种方法(即初始化程序),请使用它,因为对象的创建在所有构造函数之间共享。

如果您必须将参数传递给来自A()参数列表的B(),则第一个选项是唯一可用的选项,因为您无法将参数传递给字段初始值设定项。 / p>

答案 3 :(得分:1)

实际上没有区别。编译器会将初始化代码移动到构造函数体中。这只是你的代码准备就绪。

答案 4 :(得分:1)

对象创建是生命周期管理问题的一部分。这通常是您想要单独处理的责任。常见的模式是:

  • 依赖注入
  • 建造者,抽象工厂或工厂方法
  • 单身,原型或对象池

要阅读这些内容,请参阅creational patternsdependency injection的Google搜索结果。

我将重点关注依赖注入,因为它是匹配您的示例的最简单方法,并产生干净,可测试的代码。其他最喜欢的模式是建筑商或工厂。

依赖注入(或控制反转)是一种非常常见的模式(虽然通常不称为模式,但我认为它是一种模式)。最知名的框架提供D.I.是Spring或Java自己的CDI,但您也可以手动完成,如下所示。

构造函数注入

在您的示例中,依赖注入的简单使用可以这样工作:

public Class Dependency {
}

public Class Application {

    private final Dependency dependency;

    public Application (Dependency dependency) {
          this.dependency = dependency;
    }
}

用法:

public static void main(String[] args) {
    Application application = new Application(new Dependency());
}

请注意,Application未创建Dependency,但希望通过构造函数向其提供Dependency的实例。这称为构造函数注入。这种方式的优点是您可以使依赖字段最终。缺点是,对于许多依赖项,构造函数可能会获得太多参数以保持可理解性。为避免这种情况,您可以使用构建器模式,或使用setter注入:

Setter Injection

public Class Dependency {
    public String doStuff(String input) {
        return input + " stuffed";
    }
}

public Class Application {

    private Dependency dependency;

    public void setDependency(Dependency dependency) {
          this.dependency = dependency;
    }

    public String letsGo(String input) {
        String stuff = dependency.doStuff(input);
        return "I called my dependency and it said: " + stuff ;
    }
}

用法:

public static void main(String[] args) {
    Application application = new Application();
    application.setDependency(new Dependency());
    System.our.println(application.letsGo("rabbit"));
}

输出:

I called my dependency and it said: rabbit stuffed

<强>测试

现在关于可测试性。请记住,在单元测试中,我希望能够在不测试类Application的情况下测试类Dependency。如果Application正在创建自己的Dependency实例,那么这很难做到。通过依赖注入,它很容易。这是一个使用JUnit和Mockito注释的例子,这是非常标准的:

@RunWith(MockitoJUnitRunner.class)
public class ApplicationTest {

    @InjectMocks
    private Application instance;
    @Mock
    private Dependency dependency;

    @Test
    public void test() {
        // SETUP
        String testStuff = "some test stuff";
        String input = "Micky";           
        when(dependency.doStuff(input)).thenReturn(testStuff);            

        // CALL
        String actualResult = instance.letsGo(input);

        // VERIFY
        verify(dependency).doStuff(input);
        String expectedResult = "I called my dependency and it said: " + testStuff;
        assertEquals(actualResult, expectedResult); 
    }
}

此处Mockito正在为您创建Application的实例,并将注释了@Mock的任何字段注入其中。它通过构造函数或setter自动确定这样做。这一行:

when(dependency.doStuff(input)).thenReturn(testStuff);

指示模拟Dependency在调用时返回特定值。这意味着我们不会调用真正的Dependency代码,而是要求Mockito在调用该类时从该类生成虚假结果。

调用Application实例后,您可以检查对依赖项的预期调用是否按如下方式进行:

verify(dependency).doStuff(input);

Mockito会检查指定的调用是否只发生一次,如果不是这样,则会生成错误。

然后使用assertEquals将实际结果与预期结果进行比较。如果值不相等,则在运行测试时JUnit会生成错误。

因此,所有这一切的主要教训是,对象创建是一个重要的问题,理想情况下应该使用其中一个创建模式或依赖注入来单独处理。通过这样做,您可以专注于完成其主要任务,使其更容易理解和测试。

<强>说明

D.I。本身可能不被认为是一种模式,因为它不够具体 - 你可以用几种方式来构建它。尽管如此,每当你看到代码创建自己的依赖项的实例时,你应该考虑代码的味道并尝试重构它。

D.I.的Spring实现使用称为应用程序上下文的抽象工厂。 Spring只需使用@Autowired(或@Inject)注释字段即可支持注入:

@Autowired
private Dependency dependency;

Spring从应用程序上下文获取Dependency实例,该上下文通常是XML file或带有@Configuration注释的类。在应用程序上下文中,您可以指定存在哪些依赖项(bean)以及它们应如何设置和连接在一起。