2

一个简单(且冗长)的问题,而不是一个简单的答案。使用一些 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<> 而不是 List<>,但我们假设这不是必需的。

创建列表的类如下(使用任何其他接口标准):

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 方法的 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 中。这将为我们提供正确的数据,但代价是耦合。此外,StringValidator 将承担实际调用依赖项的额外责任。

如果我以这种方式使用委托,我的代码就会被不需要的责任(不是我想要的)弄得乱七八糟。

如果我不这样做,我看不出有什么方法可以工作(提供者可以为我提供正确的列表,但同样,存在依赖关系)。

更普遍的问题是 - 有没有办法使用 DI 框架实际创建一个完全解耦的应用程序,或者这是某种理想?您在哪些情况下使用 DI 框架,在哪些情况下不使用?DI 框架真的是“新的”吗?

谢谢你。

4

3 回答 3

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();
}

注意:不是线程安全的。在线程环境中,我肯定会采取额外的步骤来消除将结果存储在字段中并调用额外的方法调用来获取它的需要。

于 2012-04-30T14:16:07.133 回答
0

我终于觉得我明白了!抱歉,我的问题中缺少信息。Ryan Stewart写道:“没有理由您的“stringList”和“validationList”不能由 DI 容器管理并注入到您的 StringValidator。”,也许他有这个想法。如果你这样做了,那就是我正在寻找的答案,你的答案是正确的,所以谢谢你。我是通过在 Spring 中进行实验自己发现的。

如果我使用包含列表的类,则生成的类无法接收列表。它们是动态创建的,我看不到将它们带到 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();
    }
}

这似乎是可能的。因此,回顾一下答案 - 您始终可以在容器中动态创建类,并且可以很好地解耦!

于 2012-05-02T13:28:27.027 回答
0

我有点困惑,但你的意思是你想在不依赖任何类的情况下使用 DI?这可能通过注解或自定义类加载器实现,但它会很慢而且很难做到。也许你可以澄清你想要什么?

于 2012-04-30T14:13:50.853 回答