一个简单(而且冗长)的问题,而不是一个简单的答案。使用一些DI框架(Spring,Guice)我得出结论,其他人提出的一些实践并不是那么简单。我似乎真的陷入了这个层面。
我会尝试尽可能简单地介绍这一点,即使有些细节可能会丢失。我希望这个问题很清楚。
假设我有一个StringValidator,一个负责验证字符串的简单类。
package test;
import java.util.ArrayList;
import java.util.List;
public class StringValidator {
private final List<String> stringList;
private final List<String> validationList;
private final List<String> validatedList = new ArrayList<String>();
public StringValidator(final List<String> stringList, final List<String> validationList) {
this.stringList = stringList;
this.validationList = validationList;
}
public void validate() {
for (String currentString : stringList) {
for (String currentValidation : validationList) {
if (currentString.equalsIgnoreCase(currentValidation)) {
validatedList.add(currentString);
}
}
}
}
public List<String> getValidatedList() {
return validatedList;
}
}
依赖性是最低的,允许这样的简单测试:
package test;
import org.junit.Assert;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class StringValidatorTest {
@Test
public void testValidate() throws Exception {
//Before
List<String> stringList = new ArrayList<String>();
stringList.add("FILE1.txt");
stringList.add("FILE2.txt");
final List<String> validationList = new ArrayList<String>();
validationList.add("FILE1.txt");
validationList.add("FILE20.txt");
final StringValidator stringValidator = new StringValidator(stringList, validationList);
//When
stringValidator.validate();
//Then
Assert.assertEquals(1, stringValidator.getValidatedList().size());
Assert.assertEquals("FILE1.txt", stringValidator.getValidatedList().get(0));
}
}
如果我们想要进一步提高灵活性,我们可以使用Collection&lt;&gt;而不是List&lt;&gt;,但我们假设没有必要。
创建列表的类如下(使用任何其他接口标准):
package test;
import java.util.List;
public interface Stringable {
List<String> getStringList();
}
package test;
import java.util.ArrayList;
import java.util.List;
public class StringService implements Stringable {
private List<String> stringList = new ArrayList<String>();
public StringService() {
createList();
}
//Simplified
private void createList() {
stringList.add("FILE1.txt");
stringList.add("FILE1.dat");
stringList.add("FILE1.pdf");
stringList.add("FILE1.rdf");
}
@Override
public List<String> getStringList() {
return stringList;
}
}
和
package test;
import java.util.List;
public interface Validateable {
List<String> getValidationList();
}
package test;
import java.util.ArrayList;
import java.util.List;
public class ValidationService implements Validateable {
private final List<String> validationList = new ArrayList<String>();
public ValidationService() {
createList();
}
//Simplified...
private void createList() {
validationList.add("FILE1.txt");
validationList.add("FILE2.txt");
validationList.add("FILE3.txt");
validationList.add("FILE4.txt");
}
@Override
public List<String> getValidationList() {
return validationList;
}
}
我们有一个Main类,主要方法是:
package test;
import java.util.List;
public class Main {
public static void main(String[] args) {
Validateable validateable = new ValidationService();
final List<String> validationList = validateable.getValidationList();
Stringable stringable = new StringService();
final List<String> stringList = stringable.getStringList();
//DI
StringValidator stringValidator = new StringValidator(stringList, validationList);
stringValidator.validate();
//Result list
final List<String> validatedList = stringValidator.getValidatedList();
}
}
所以我们假设这些类在运行时生成列表(当用户想要时)。 “直接”(静态)绑定是不可能的。
如果我们想提供尽可能低的耦合,我们将使用这些列表为我们提供运行验证所需的数据(StringValidator)。
但是,如果我们想使用容器帮助我们使用“粘合代码”,我们可以将两个“服务”注入StringValidator。这将为我们提供正确的数据,但代价是COUPLING。此外,StringValidator还有额外的责任实际调用依赖项。
如果我以这种方式使用委托,我会使用不需要的责任(而不是我想要的东西)使我的代码混乱。
如果我不这样做,我看不出这种方式可行的方式(提供商可以为我提供正确的列表,但是,依赖性就在那里)。
更通用的问题是 - 有没有办法使用DI框架实际创建一个完全解耦的应用程序,或者这是某种理想的? 在哪种情况下你使用DI框架,你不是吗? DI框架真的是“新的”吗?
谢谢。
答案 0 :(得分:4)
这是一个有点难以回答的问题,特别是因为它是一个人为的例子,但如果我们假设您的类已经完全按照您的意愿设计,那么依赖注入的正确应用就很简单了。你似乎专注于你的StringValidator的可测试性,并试图通过依赖注入尝试做一些神奇的事情。 应该关注可测试性的地方在您的Main类中。这就是你引入紧耦合和不可测试代码的地方,它就是DI容器显示其价值的地方。正确应用DI和IoC原则可能会产生类似的结果:
public class Main {
@Autowired
private Validateable validateable;
@Autowired
private Stringable stringable;
public void main() {
final List<String> validationList = validateable.getValidationList();
final List<String> stringList = stringable.getStringList();
StringValidator stringValidator = new StringValidator(stringList, validationList);
stringValidator.validate();
final List<String> validatedList = stringValidator.getValidatedList();
}
public static void main(String[] args) {
Container container = new ...;
container.get(Main.class).main();
}
}
换句话说,您的所有手动接线都会被转移到DI容器的控制位置。这就是重点。就个人而言,我对此不满意,因为你仍然有一些看起来像“组件”类的东西 - StringValidator - 由你的代码实例化。我会研究重新设计事物的方法,以消除代码中的这种硬依赖,并将该创建转移到容器中。
对于这个“新的新”,不,DI容器不是新的。他们已经存在了很长一段时间。如果你的意思是“我应该使用一个吗?”,那么我猜我的回答通常是“是”,尽管这种模式比任何特定的实现更重要。这些好处已得到很好的建立和接受,而且它更像是一种思考方式而非实际框架。正如我刚刚演示的那样,您的Main类本质上是一个原始的DI容器。
更新:如果您主要关心的是如何处理StringValidator的输入,则有几个选项。没有理由你的“stringList”和“validationList”不能由DI容器管理并注入到你的StringValidator中。然后这些列表的来源取决于容器。它们可能来自您的其他物体或通过测试注入。或者,也许您正在寻找改变StringValidator如何获取其输入的抽象?在这种情况下,也许这样的事情会更好地满足您的需求:
public class StringValidator {
private SourceOfStrings stringSource;
private SourceOfStrings validationStringSource;
private final List<String> validatedList = new ArrayList<String>();
...
public void validate() {
for (String currentString : stringSource.getStrings()) {
for (String currentValidation : validationStringSource.getStrings()) {
if (currentString.equalsIgnoreCase(currentValidation)) {
validatedList.add(currentString);
}
}
}
}
public List<String> getValidatedList() {
return validatedList;
}
}
interface SourceOfStrings {
List<String> getStrings();
}
注意: NOT 线程安全。在线程环境中,我肯定会采取额外步骤来消除将结果存储在字段中的需要,并调用额外的方法调用来获取它。
答案 1 :(得分:0)
我有点困惑,但你的意思是你想要使用DI而不依赖于任何课程?这可能是通过注释或自定义类加载器实现的,但它会很慢而且难以置信。也许你可以澄清你想要的东西?
答案 2 :(得分:0)
我终于认为我明白了!很抱歉我的问题中缺少信息。 Ryan Stewart 写道:“没有理由你的”stringList“和”validationList“无法通过DI容器进行管理并注入到你的StringValidator中。”,也许他考虑到了这一点。如果你这样做,那是我正在寻找的答案,你的答案是正确的,谢谢你。我是在春天尝试自己找到的。
如果我使用包含列表的类,则生成的类无法重新列出列表。它们是动态创建的,我看不到将它们带到StringValidator。动态意味着 - 无需控制容器。
我可以注意到它们的唯一方法是将它们直接注入StringValidator。
但是我忘记了一件事。 Spring更灵活(根据我的经验) - 坦率地说我不知道如何在Guice中解决这个问题(还没有真正尝试过,也许我会试一试)。为什么不动态创建列表,并在容器生命周期中使用该列表作为可用于注入所需类的列表?
重点是,当容器初始化列表时:
package test;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component
public class StringList extends ArrayList<String> {
}
package test;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component
public class ValidationList extends ArrayList<String> {
}
或者如果您更喜欢xml方式(已注释):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<context:component-scan base-package="test"/>
<!--<bean id="validationList" class="java.util.ArrayList" scope="singleton"/>-->
<!--<bean id="stringList" class="java.util.ArrayList" scope="singleton"/>-->
</beans>
该列表可以在容器的生命周期中使用,因此也可以在应用程序中使用。
package test;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@Component
public class StringService implements Stringable {
private List<String> stringList;
@Inject
public StringService(final ArrayList<String> stringList) {
this.stringList = stringList;
createList();
}
//Simplified
private void createList() {
stringList.add("FILE1.txt");
stringList.add("FILE1.dat");
stringList.add("FILE1.pdf");
stringList.add("FILE1.rdf");
}
@Override
public List<String> getStringList() {
return stringList;
}
}
package test;
import org.springframework.stereotype.Component;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@Component
public class ValidationService implements Validateable {
private List<String> validationList;
@Inject
public ValidationService(final ArrayList<String> validationList) {
this.validationList = validationList;
createList();
}
//Simplified...
private void createList() {
validationList.add("FILE1.txt");
validationList.add("FILE2.txt");
validationList.add("FILE3.txt");
validationList.add("FILE4.txt");
}
@Override
public List<String> getValidationList() {
return validationList;
}
}
而且,我不必担心这些服务,因为这些列表现在位于容器中,它们存在自己的生命周期,因此每次请求它们时都可以使用。
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class StringValidator {
private List<String> stringList;
private List<String> validationList;
private final List<String> validatedList = new ArrayList<String>();
@Autowired
public StringValidator(final ArrayList<String> stringList,
final ArrayList<String> validationList) {
this.stringList = stringList;
this.validationList = validationList;
}
public void validate() {
for (String currentString : stringList) {
for (String currentValidation : validationList) {
if (currentString.equalsIgnoreCase(currentValidation)) {
validatedList.add(currentString);
}
}
}
}
public List<String> getValidatedList() {
return validatedList;
}
}
答案实际上看起来非常简单,但在我到达这里之前花了一些时间。
因此,Main类看起来像这样,所有内容都由容器处理。
package test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class Main {
@Autowired
private StringValidator stringValidator;
public void main() {
stringValidator.validate();
final List<String> validatedList = stringValidator.getValidatedList();
for (String currentValid : validatedList) {
System.out.println(currentValid);
}
}
public static void main(String[] args) {
ApplicationContext container = new ClassPathXmlApplicationContext("/META-INF/spring/applicationContext.xml");
container.getBean(Main.class).main();
}
}
似乎有可能。所以回顾一下答案 - 你总是可以在容器中拥有动态创建的类,并且可以很好地解耦!