2012-04-30 3 views
2

Простой (и длительный) вопрос, а не простой ответ. Работая с некоторыми рамками DI (Spring, Guice), я пришел к выводу, что некоторые из практик, представленные другими, не так просто реализовать. На самом деле я действительно застрял на этом уровне.Зависимость от впрыска, замедленная инъекция praxis

Я постараюсь представить это как можно более простым, хотя некоторые детали, вероятно, будут потеряны. Надеюсь, этот вопрос будет ясен.

Скажем, у меня есть 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)); 
    } 
} 

Если мы хотим, чтобы увеличить гибкость даже больше, мы могли бы использовать коллекцию <> вместо списка <>, но давайте предположим, что это не понадобится.

Классов, которые создают списки являются следующими (использовать любой другой стандартный интерфейс):

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

И у нас есть основной класс с основным методом:

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 действительно «новыми новыми»?

спасибо.

ответ

0

Я, наконец, думаю, что получил! Извините за недостаток информации в моем вопросе. Ryan Stewart написал: «Нет причин, по которым ваш« stringList »и« validationList »не может управляться контейнером DI и вводится в ваш StringValidator.», И, возможно, он имел это в виду. Если бы вы это сделали, то это был ответ, который я искал, и ваш ответ правильный, так что спасибо. Я нашел это сам, экспериментируя весной.

Если я использую классы, содержащие списки, то результирующий класс не может получить списки. Они динамически создаются, и я не видел способа привести их в StringValidator. Динамически означает - без контроля контейнера.

Единственный способ, которым я мог нанести им вред, - это ввести их прямо в StringValidator.

Но я забыл одно. Весна гораздо более гибкая (основанная на моем опыте) - я откровенно не знаю, как я мог бы решить это в Guice (на самом деле я не пробовал, может быть, я отдам его).

Почему бы не создать список динамически и использовать этот список в ресурсе контейнера как список, который можно использовать для ввода требуемого класса?

enter image description here

Суть в том, когда контейнер инициализирует список:

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

Кажется возможным. Таким образом, чтобы ответить на этот вопрос, вы всегда можете иметь динамически созданный класс в контейнере и довольно неплохую развязку!

0

Я немного смущен, но это то, что вы имеете в виду, что хотите использовать DI, не зависимо от каких-либо классов? Это может быть возможно с помощью аннотаций или пользовательского загрузчика классов, но это было бы медленно и невероятно сложно. Может быть, вы могли бы уточнить, что вы хотели?

+0

Нет, я имел в виду использование рамки DI для кода «клей» - архитектурный код и удаление любых очевидных прямых зависимостей для достижения большей гибкости. Должны быть зависимости, но скрыты внутри контейнера. Извините, если это несколько абстрактно, но для того, чтобы действительно задать правильный вопрос с конкретным примером - это займет страницы. – pfh

4

Это довольно сложный вопрос для ответа, особенно потому, что это надуманный пример, но если мы предположим, что ваши классы уже разработаны именно так, как вы того захотите, правильное применение инъекции зависимостей здесь простое. Кажется, вы ориентируетесь на тестируемость вашего 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 не новы. Они были довольно долгое время. Если вы имеете в виду: «Должен ли я использовать один?», То, я думаю, мой ответ был бы вообще «да», хотя шаблон более важен, чем любая конкретная реализация. Преимущества хорошо установлены и приняты, и это скорее способ мышления, чем реальная структура. Как я только что продемонстрировал, ваш основной класс был по сути примитивным контейнером 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(); 
} 

Примечание: НЕ потокобезопасна. В многопоточной среде я определенно сделаю дополнительный шаг, чтобы удалить необходимость сохранения результата в поле и вызвать дополнительный вызов метода для его получения.

+0

Спасибо за ответ. Это новый способ взглянуть на проблему. Вы оставили динамическую часть и решили «статическую» привязку. То, что я имел в виду, было: контейнеры DI - это новые новые (ключевое слово для создания экземпляра). Это выглядит лучше, но все равно не решает проблему. Как бы вы «переделали вещи»? Это проблема. Я не могу понять, как кто-то перепроектирует что-то подобное и придумает «ясное» решение. Вы подняли зависимости на один уровень выше (или вниз), но проблема осталась. И если у вас есть что-то динамичное, вы не можете использовать контейнер полностью. – pfh

+0

Обновлен мой ответ –

+0

Другими словами, вы говорите, что нет волшебного пути (я надеялся на какой-то практический «шаблон»), чтобы фактически полностью отделить этот пример. Да, теперь у вас есть еще более абстрактный интерфейс, но исходные интерфейсы корректно работали. Теперь у меня все еще есть зависимость от SourceOfStrings (или Validateable или Stringable), и если я хочу использовать их, как вы показали, я не могу «обойти» инъекцию службы в классе. Это загромождает мой тестовый код (да, я знаю, как использовать насмешку, но это не так), и если я использую какое-то динамическое связывание, мне все равно придется называть getStrings ленивым. – pfh