[英]Dependency injection, delayed injection praxis
一個簡單(冗長)的問題,而不是簡單的答案。 通過使用某些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();
}
}
因此,假設類在運行時(當用戶需要時)生成列表。 “直接”(靜態)綁定是不可能的。
如果我們想提供盡可能低的耦合,我們將使用列表為我們提供運行validation(StringValidator)所需的數據。
但是,如果我們想使用容器來幫助我們處理“膠水代碼”,則可以將兩個“服務”注入StringValidator。 這樣可以為我們提供正確的數據,但是要付出耦合的代價。 而且,StringValidator將額外承擔實際調用依賴項的責任。
如果以這種方式使用委派,則會使代碼混亂,並產生不必要的責任(不是我想要的東西)。
如果我不這樣做,我看不到有什么辦法可以做到這一點(Provider可以為我提供正確的列表,但是依賴項仍然存在)。
更為籠統的問題是-是否有一種方法可以使用DI框架實際創建完全解耦的應用程序,或者這是理想的選擇嗎? 在這種情況下,您是否使用DI框架? DI框架真的是“新”嗎?
謝謝。
這是一個很難回答的問題,尤其是因為它是一個人為的示例,但是如果我們假設您的類已經按照您的要求進行了完全正確的設計,那么這里的依賴注入的正確應用就很簡單。 您似乎專注於StringValidator的可測試性,並嘗試通過依賴項注入對其做一些魔術。 您應該擔心可測試性的地方是您的主班。 那就是您引入緊密耦合和不可測試的代碼的地方,而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的輸入,則有兩種選擇。 沒有理由不能由DI容器管理“ stringList”和“ validationList”並將其注入到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();
}
注意: 不是線程安全的。 在線程環境中,我肯定會采取額外的步驟來消除將結果存儲在字段中並調用額外的方法調用以獲取結果的需要。
我有些困惑,但是您想使用DI而不依賴任何類是什么意思? 這可以通過注釋或自定義類加載器來實現,但這會很慢並且難以執行。 也許您可以澄清您想要什么?
我終於覺得我明白了! 對不起,我的信息不足。 賴安·斯圖爾特 ( Ryan Stewart)寫道:“沒有理由不能通過DI容器來管理您的“ stringList”和“ validationList”並注入到您的StringValidator中。”也許他想到了這一點。 如果您這樣做了,那么那不是我一直在尋找的答案,而且您的答案是正確的,所以謝謝。 我自己是通過在春季進行實驗而發現的。
如果使用包含列表的類,則生成的類將無法接收列表。 它們是動態創建的,我看不到將它們帶到StringValidator的方法。 動態方式-無需控制容器。
我可以注入它們的唯一方法是將它們直接注入StringValidator。
但是我忘了一件事。 春天更具彈性(根據我的經驗)-坦率地說,我不知道如何解決這個問題(還沒有真正嘗試過,也許我會嘗試一下)。
為什么不動態創建列表,並在容器生命周期中將該列表用作可用於注入所需類的列表?
關鍵是,當容器初始化列表時:
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();
}
}
似乎有可能。 因此,概括一下答案-您始終可以在容器中擁有動態創建的類,並且可以實現很好的解耦!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.