Понимание необходимости структуры DI - PullRequest
39 голосов
/ 01 февраля 2009

Это может быть наивный вопрос. В настоящее время я изучаю среду Spring и внедрение зависимостей . Хотя базовый принцип DI довольно легко понять, не сразу понятно, зачем вам нужна сложная структура для его реализации.

Рассмотрим следующее:

public abstract class Saw
{
    public abstract void cut(String wood);
}

public class HandSaw extends Saw
{
    public void cut(String wood)
    {
        // chop it up
    }
}

public class ChainSaw extends Saw
{
    public void cut(String wood)
    {
        // chop it a lot faster
    }
}

public class SawMill
{
    private Saw saw;

    public void setSaw(Saw saw)
    {
        this.saw = saw;
    }

    public void run(String wood)
    {
        saw.cut("some wood");
    }
}

Тогда вы можете просто сделать:

Saw saw = new HandSaw();
SawMill sawMill = new SawMill();
sawMill.setSaw(saw);
sawMill.run();

Что будет эквивалентно:

<bean id="saw" class="HandSaw"/>

<bean id="sawMill" class="SawMill">
   <property name="saw" ref="saw"/>
</bean>

плюс:

ApplicationContext context = new ClassPathXmlApplicationContext("sawmill.xml");
SawMill springSawMill = (SawMill)context.getBean("sawMill");
springSawMill.run();

Конечно, это надуманный пример, и с более сложными объектными отношениями может быть более эффективно хранить XML-файл, чем записывать его программно, но, несомненно, в нем должно быть что-то большее?

(Я знаю, что среда Spring - это нечто большее, но я думаю о необходимости контейнера DI.)

В первом примере также было бы тривиально изменить зависимости в середине потока:

// gotta chop it faster
saw = new ChainSaw();
sawMill.setSaw(saw);
sawMill.run();

Ответы [ 12 ]

15 голосов
/ 01 февраля 2009

У меня был точно такой же вопрос, и на него ответили:
Конечно, вы можете сделать то, что вы описали в «Тогда вы можете просто сделать: ...» (давайте назовем это «класс А»). Однако это связывает класс A с HandSaw или со всеми зависимостями, необходимыми для класса SawMill. Почему A должен быть связан с HandSaw - или, если вы выберете более реалистичный сценарий, почему моя бизнес-логика должна быть связана с реализацией соединения JDBC, необходимой для уровня DAO?
Тогда я предложил решение «затем переместить зависимости на один шаг дальше» - хорошо, так что теперь я связал свое представление с соединением JDBC, где я должен иметь дело только с HTML (или Swing, выберите свой вариант).

Инфраструктура DI, сконфигурированная с помощью XML (или JavaConfig), решает эту проблему, позволяя просто «получить необходимую услугу». Вам все равно, как он инициализируется, что ему нужно для работы - вы просто получаете сервисный объект и активируете его.

Кроме того, у вас есть неправильное представление о плюсе: (где вы делаете SawMill springSawMill = (SawMill)context.getBean("sawMill"); springSawMill.run();) - вам не нужно получать bean-компонент sawMill из контекста - bean-компонент sawMill должен был быть внедрен в ваш объект (класс А) по схеме DI. поэтому вместо ... getBean (...) вам нужно просто перейти к "sawMill.run ()", не заботясь о том, откуда он, кто его инициализировал и как. Как ни крути, он может перейти прямо к / dev / null, или к тесту, или к реальному движку CnC ... Дело в том, что вам все равно. Все, что вас волнует, это ваш крошечный маленький класс А, который должен делать то, что по контракту - активировать лесопильный завод.

15 голосов
/ 01 февраля 2009

Внедрение зависимостей является вырожденной формой неявной передачи параметров , и цель, по сути, та же, решить то, что называется Проблема конфигураций :

Проблема конфигурации заключается в распространять настройки во время выполнения на протяжении всей программы, позволяя несколько одновременных наборов конфигурации безопасно сосуществовать под статически гарантированное разделение.

Инъекция Dependency Injection компенсирует отсутствие неявных параметров , Curry-функций и удобных средств для монад в языке.

12 голосов
/ 01 февраля 2009

Spring имеет три одинаково важные функции:

  1. внедрение зависимости
  2. аспектно-ориентированное программирование
  3. библиотека классов фреймворка, чтобы помочь с постоянством, удаленным доступом, веб-MVC и т. Д.

Я согласен, что трудно увидеть преимущество внедрения зависимостей, когда вы сравниваете это с одним вызовом new. В этом случае последний, безусловно, будет выглядеть проще, потому что это одна строка кода. Конфигурация Spring всегда увеличивает количество строк кода, поэтому это не выигрышный аргумент.

Это начинает выглядеть намного лучше, когда вы можете исключить из своих классов такие сквозные проблемы, как транзакции, и использовать аспекты для их декларативной настройки. Сравнение с одним «новым» вызовом - это не то, для чего был создан Spring.

Возможно, лучшим результатом использования Spring будет то, как его рекомендуемая идиома использует интерфейсы, многоуровневую компоновку и такие хорошие принципы, как DRY. В действительности это всего лишь отбор объектно-ориентированных лучших практик, которые Род Джонсон использовал в своих консультационных выступлениях. Он обнаружил, что созданный им со временем код помог ему заработать, предоставляя лучшее программное обеспечение для своих клиентов. Он подытожил свой опыт в «Эксперте 1: 1 J2EE» и закончил с открытым исходным кодом для кода Spring.

Я бы сказал, покупайся в фреймворк в той степени, в которой ты думаешь, что его опыт может помочь тебе написать лучший код.

Я не думаю, что вы сможете получить полное представление о Spring, пока не объедините все эти три функции.

6 голосов
/ 01 февраля 2009

Конечно, это надуманный пример, и с более сложными объектными отношениями может быть эффективнее сохранить XML-файл, чем записать его программно, но, несомненно, в нем должно быть что-то большее?

Я думаю, что имеет смысл поместить «разводку» в файл конфигурации, а не делать это вручную в коде по нескольким причинам:

  1. Конфигурация является внешней по отношению к вашему коду.
  2. Изменения в подключении (чтобы указать sawmill использовать другой экземпляр Saw) могут быть просто внесены во внешний (XML) файл и не требуют изменения кода, повторной компиляции, повторного развертывания и т. д.
  3. Когда у вас есть десятки классов и несколько уровней внедрения (например: у вас есть веб-класс Controller, который получает класс Service, содержащий вашу бизнес-логику, который использует DAO для получения Saw s из базы данных, в которую вставляется DataSource и т. д.), ручное подключение коллабораторов утомительно и требует нескольких десятков строк кода, которые ничего не делают, кроме подключения.
  4. Это несколько менее очевидное «преимущество», но, учитывая, что все «подключения» являются внешними по отношению к коду, я думаю, что это помогает укрепить разработчикам идеи, лежащие в основе внедрения зависимости, в частности идея кодирования интерфейса, а не реализации. С ручным подключением может быть легко вернуться к старому.
5 голосов
/ 01 февраля 2009

Меня, как правило, не волнует DI на основе XML или Reflection, потому что в моих случаях использования это добавляет ненужную сложность. Вместо этого я обычно обращаюсь к какой-либо форме ручного ДИ, которая для меня кажется более естественной и имеет большинство преимуществ.

public class SawDI{
    public Saw CreateSaw(){
        return new HandSaw();
    }

    public SawMill CreateSawMill(){
        SawMill mill = new SawMill();
        mill.SetSaw(CreateSaw());
        return mill;
    }
}

// later on

SawDI di = new SawDI();
SawMill mill = di.CreateSawMill();

Это означает, что я все еще централизую связь и имею все преимущества этого, без зависимости от более сложной структуры DI или файлов конфигурации XML.

3 голосов
/ 29 июля 2010

Не забывайте об одном существенном недостатке внедрения зависимостей: вы теряете возможность легко выяснять, откуда что-то инициализируется, используя Find Usages вашей мощной Java IDE. Это может быть очень серьезным моментом, если вы много раз проводите рефакторинг и хотите избежать того, чтобы тестовый код был в 10 раз больше кода приложения.

3 голосов
/ 01 февраля 2009

Одна вещь, которую большинство (если не все) DI-контейнеров / библиотек приносят вам дополнительно, это возможность перехватывать методы для всех экземпляров, созданных с помощью DI.

2 голосов
/ 13 марта 2011

В последнее время большое внимание уделяется DI каркасам , даже при том, что паттерн DI забывается. Принципы Д.И. как обобщены Дж. Б. Рейнсбергер :

Это просто: сделать зависимости явными, требуя от соавторов параметры в конструкторе. Повторите, пока вы не подтолкнули все решения о том, какие объекты создавать в точке входа. Конечно, это только относится к услугам (в смысле DDD). Готово.

Как вы заметили, нет большой разницы между настройкой зависимостей вручную и использованием фреймворка. С внедрением конструктора, как показано ниже, ваш пример кода будет иметь еще меньше шаблонов, и компилятор заставит вас предоставить все необходимые зависимости (и ваша IDE, вероятно, напечатает их для вас).

SawMill sawMill = new SawMill(new HandSaw());
sawMill.run();

Структура DI может уменьшить шаблон создания фабрик и связывания объектов вместе, но в то же самое время это может также затруднить обнаружение того, откуда происходит каждая зависимость - конфигурация структуры DI - это еще один уровень для изучения абстракции, и ваша среда IDE не сможет сказать вам, откуда вызывается конкретный конструктор.

Косвенным недостатком платформ DI является то, что они могут сделать подключение зависимостей слишком простым . Когда вы больше не можете чувствовать боль от множества зависимостей, вы можете просто продолжать добавлять больше зависимостей вместо того, чтобы переосмыслить дизайн приложения, чтобы уменьшить связь между классами. Соединение зависимостей вручную - особенно в тестовом коде - позволяет легче заметить, когда у вас слишком много зависимостей, так как настройка теста становится длиннее и становится сложнее писать модульные тесты.

Некоторые преимущества DI-фреймворков заключаются в их поддержке расширенных функций, таких как AOP (например, Spring @Transactional), области видимости (хотя много раз области видимости с простым кодом будет достаточно) и возможности подключения (если действительно необходим плагин фреймворка).

Недавно я провел эксперимент с ручным DI и DI на основе фреймворка. Прогресс и результаты показаны в виде скринкастов в Let's Code Dimdwarf эпизодах с 42 по 47 . В рассматриваемом проекте использовалась система плагинов Guice для создания актеров , которую я затем переписал с использованием ручного DI без Guice. В результате была реализована гораздо более простая и понятная реализация, и лишь немного больше шаблонов.

Сводка: Сначала попробуйте использовать только ручной DI (предпочтительно инжектор конструктора). Если их становится много, попробуйте переосмыслить дизайн, чтобы уменьшить зависимости. Используйте DI-фреймворк, если некоторые его функции обеспечивают вам ценность.

2 голосов
/ 01 февраля 2009

Если вы жестко закодировали вставленный класс, вам нужно, чтобы этот класс был доступен во время компиляции. С помощью файла конфигурации вы можете изменить используемую пилу (в вашем случае) во время выполнения без перекомпиляции и даже использовать пилу, взятую из новой банки, которую вы только что поместили в путь к классам. Если это того стоит, сложность зависит от задачи, которую вы должны решить.

1 голос
/ 08 февраля 2009

Одним из самых больших преимуществ использования Dependency Injection является то, что он значительно упрощает предоставление макетов или заглушек зависимостей класса при создании модульного теста для этого класса. Это позволяет вам тестировать класс изолированно, независимо от его соавторов.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...