给定两个类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
}
}
那么两种解决方案有什么区别?
答案 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 patterns和dependency 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)以及它们应如何设置和连接在一起。