Каковы преимущества контейнеров для инъекций зависимости? - PullRequest
102 голосов
/ 25 сентября 2008

Я понимаю преимущества самого внедрения зависимости. Давайте возьмем весну например. Я также понимаю преимущества других функций Spring, таких как AOP, помощники разных типов и т. Д. Мне просто интересно, каковы преимущества конфигурации XML, такие как:

<bean id="Mary" class="foo.bar.Female">
  <property name="age" value="23"/>
</bean>
<bean id="John" class="foo.bar.Male">
  <property name="girlfriend" ref="Mary"/>
</bean>

по сравнению с простым старым кодом Java, таким как:

Female mary = new Female();
mary.setAge(23);
Male john = new Male();
john.setGirlfriend(mary);

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


UPDATE:
В случае

IService myService;// ...
public void doSomething() {  
  myService.fetchData();
}

Как IoC Framework может угадать, какую реализацию myService я хочу внедрить, если их несколько? Если есть только одна реализация данного интерфейса, и я позволю контейнеру IoC автоматически решить использовать его, он будет нарушен после появления второй реализации. И если намеренно существует только одна возможная реализация интерфейса, вам не нужно внедрять его.

Было бы очень интересно увидеть небольшой фрагмент конфигурации для IoC, который демонстрирует его преимущества. Я давно пользуюсь Spring и не могу привести такой пример. И я могу показать отдельные строки, которые демонстрируют преимущества hibernate, dwr и других фреймворков, которые я использую.


ОБНОВЛЕНИЕ 2:
Я понимаю, что конфигурацию IoC можно изменить без перекомпиляции. Это действительно такая хорошая идея? Я могу понять, когда кто-то хочет изменить учетные данные БД без перекомпиляции - он может быть не разработчиком. Как часто в вашей практике кто-то, кроме разработчика, меняет конфигурацию IoC? Я думаю, что для разработчиков нет усилий перекомпилировать этот конкретный класс вместо изменения конфигурации. А для тех, кто не является разработчиком, вы, вероятно, захотите упростить его жизнь и предоставить более простой файл конфигурации.


ОБНОВЛЕНИЕ 3:

Внешняя конфигурация отображения между интерфейсами и их конкретными реализациями

Что такого хорошего в том, чтобы сделать его экстентальным? Вы не делаете весь свой код внешним, хотя вы определенно можете - просто поместите его в файл ClassName.java.txt, прочитайте и скомпилируйте вручную на лету - вы избежали перекомпиляции. Почему следует избегать компиляции?!

Вы экономите время кодирования, потому что вы предоставляете отображения декларативно, а не в процедурном коде

Я понимаю, что иногда декларативный подход экономит время. Например, я объявляю только один раз сопоставление между свойством bean-компонента и столбцом DB, и hibernate использует это отображение при загрузке, сохранении, построении SQL на основе HSQL и т. Д. Именно здесь работает декларативный подход. В случае Spring (в моем примере), объявление имеет больше строк и имеет ту же выразительность, что и соответствующий код. Если есть пример, когда такое объявление короче кода - я бы хотел его увидеть.

Принцип инверсии управления упрощает модульное тестирование, поскольку вы можете заменить реальные реализации поддельными (например, заменить базу данных SQL на оперативную)

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

mary = new FakeFemale();

Я понимаю преимущества DI. Я не понимаю, какие преимущества добавляет внешняя конфигурация XML по сравнению с настройкой кода, который делает то же самое. Я не думаю, что следует избегать компиляции - я компилирую каждый день, и я все еще жив. Я думаю, что конфигурация DI - плохой пример декларативного подхода. Декларация может быть полезна, если она объявляется один раз и используется много раз по-разному - например, hibernate cfg, где сопоставление между свойством bean и столбцом DB используется для сохранения, загрузки, построения поисковых запросов и т. Д. Конфигурация Spring DI может быть легко преобразована в настройка кода, как в начале этого вопроса, не так ли? И это используется только для инициализации бина, не так ли? Что означает, что декларативный подход здесь ничего не добавляет, не так ли?

Когда я объявляю hibernate mapping, я просто даю hibernate некоторую информацию, и она работает на ее основе - я не говорю ей, что делать. В случае весны, мое объявление говорит весне точно, что делать - так зачем объявлять это, почему бы просто не сделать это?


ПОСЛЕДНИЕ ОБНОВЛЕНИЯ:
Ребята, многие ответы говорят мне о внедрении зависимостей, которые я ЗНАЮ, ХОРОШО. Вопрос заключается в назначении конфигурации DI вместо инициализации кода - я склонен думать, что инициализация кода короче и понятнее. Единственный ответ, который я до сих пор получил на свой вопрос, заключается в том, что он избегает перекомпиляции при изменении конфигурации. Думаю, мне следует опубликовать еще один вопрос, потому что для меня это большой секрет, почему в этом случае следует избегать компиляции.

Ответы [ 16 ]

40 голосов
/ 08 октября 2008

Для меня одной из основных причин использования IoC (и использования внешней конфигурации) являются две области:

  • Тестирование
  • Производственное обслуживание

Тестирование

Если вы разделите тестирование на 3 сценария (что вполне нормально для крупномасштабных разработок):

  1. Юнит-тестирование
  2. Интеграционное тестирование
  3. Тестирование черного ящика

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

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

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

Производство

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

Краткое описание

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

  • Приложение распространяется на несколько сайтов / клиентов, где среды будут отличаться.
  • Ограниченный контроль разработки / ввод в производственную среду и настройку.
  • Сценарии тестирования.

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

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

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

14 голосов
/ 25 сентября 2008

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

Язык должен обеспечивать хорошую поддержку как обычных методов объектно-ориентированного программирования, так и поддержки интерфейсов объектов и отражения объектов (например, Java и C #). Хотя вы можете создавать программы с использованием шаблонов DI в системах C ++, отсутствие поддержки рефлексии внутри самого языка не позволяет поддерживать серверы приложений и другие платформы DI и, следовательно, ограничивает выразительность шаблонов DI.

Сильные стороны системы, построенной с использованием шаблонов DI:

  1. Код DI гораздо проще использовать повторно, поскольку «зависимая» функциональность экстраполируется на четко определенные интерфейсы, позволяя отдельным объектам, конфигурация которых обрабатывается подходящей прикладной платформой, подключаться к другим объектам по желанию.
  2. Код DI намного проще для тестирования. Функциональность, выраженную объектом, может быть проверена в «черном ящике» путем создания «фиктивных» объектов, реализующих интерфейсы, ожидаемые логикой приложения.
  3. Код DI более гибкий. Это изначально слабо связанный код - до крайности. Это позволяет программисту выбирать, как объекты соединяются, основываясь исключительно на их необходимых интерфейсах на одном конце и их выраженных интерфейсах на другом.
  4. Внешняя (Xml) конфигурация объектов DI означает, что другие могут настраивать ваш код в непредвиденных направлениях.
  5. Внешняя конфигурация также является шаблоном разделения интересов в том смысле, что все проблемы инициализации объекта и управления взаимозависимостью объектов могут решаться сервером приложений.
  6. Обратите внимание, что для использования шаблона DI внешняя конфигурация не требуется, для простых соединений часто достаточно небольшого строительного объекта. Существует компромисс между гибкостью. Объект компоновщика не такой гибкий параметр, как внешне видимый файл конфигурации. Разработчик системы DI должен взвесить преимущества гибкости над удобством, следя за тем, чтобы мелкомасштабный, точный контроль структуры объекта, как указано в файле конфигурации, мог увеличить путаницу и затраты на техническое обслуживание в будущем.

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

Пример, приведенный в вопросе, просто затрагивает поверхность выразительной силы, которую может обеспечить должным образом разложенная библиотека объектов DI. С некоторой практикой и большой самодисциплиной большинство практиков DI находят, что они могут создавать системы, которые имеют 100% тестовое покрытие кода приложения. Один этот момент необычен. Это не 100% тестовое покрытие небольшого приложения из нескольких сотен строк кода, а 100% тестовое покрытие приложений, содержащих сотни тысяч строк кода. Я не могу описать любой другой шаблон проектирования, который обеспечивает этот уровень тестируемости.

Вы правы в том, что применение всего лишь 10-ти строк кода проще для понимания, чем несколько объектов плюс ряд файлов конфигурации XML. Однако, как и в случае с наиболее мощными шаблонами проектирования, вы получаете преимущества, поскольку вы продолжаете добавлять новые функции в систему.

Короче говоря, крупномасштабные приложения, основанные на DI, легче отлаживать и легче понимать. Хотя конфигурация Xml не проверяется во время компиляции, все службы приложений, о которых знает этот автор, будут предоставлять разработчику сообщения об ошибках, если они попытаются внедрить объект с несовместимым интерфейсом в другой объект. И большинство из них предоставляют функцию проверки, которая охватывает все известные конфигурации объектов. Это легко и быстро сделать, проверив, что объект A, подлежащий внедрению, реализует интерфейс, требуемый объектом B для всех настроенных объектов.

7 голосов
/ 25 сентября 2008

Это немного загруженный вопрос, но я склонен согласиться с тем, что огромные объемы конфигурации xml на самом деле не приносят большой пользы. Мне нравится, чтобы мои приложения были настолько легкими, насколько это возможно, в зависимости, включая здоровенные фреймворки.

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

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

Тем не менее, конфигурация XML - моя любимая мозоль ... Я стараюсь избегать ее любой ценой.

5 голосов
/ 15 апреля 2009

Каждый раз, когда вы можете изменить свой код на данные, вы делаете шаг в правильном направлении.

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

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

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

3 голосов
/ 24 февраля 2011

У меня есть ваш ответ

Очевидно, что в каждом подходе есть компромиссы, но внешние файлы конфигурации XML полезны для разработки предприятия, в которой для компиляции кода используются системы сборки, а не ваша IDE. Используя систему сборки, вы можете захотеть добавить определенные значения в ваш код - например, версию сборки (что может быть болезненно, если вам придется обновлять ее каждый раз при компиляции). Боль усиливается, когда ваша система сборки выполняет код какой-либо системы контроля версий. Изменение простых значений во время компиляции потребует от вас изменения файла, его фиксации, компиляции и последующего возврата каждый раз для каждого изменения. Это не те изменения, которые вы хотите зафиксировать в вашем контроле версий.

Другие полезные варианты использования системы сборки и внешних конфигов:

  • внедрение стилей / таблиц стилей для одной базы кода для разных сборок
  • внедрение различных наборов динамического контента (или ссылок на них) для вашей единой базы кода
  • вставка контекста локализации для разных сборок / клиентов
  • изменение URI веб-службы на резервный сервер (при отключении основного)

Обновление: Все приведенные выше примеры касались вещей, которые не обязательно требовали зависимости от классов. Но вы можете легко создавать случаи, когда необходим как сложный объект, так и автоматизация, например:

  • Представьте, что у вас есть система, в которой он отслеживает трафик вашего сайта. В зависимости от количества одновременно работающих пользователей, он включает / выключает механизм регистрации. Возможно, когда механизм выключен, на его место ставится заглушка.
  • Представьте, что у вас есть система веб-конференций, в которой, в зависимости от количества пользователей, вы хотите отключить возможность проводить P2P в зависимости от количества участников
3 голосов
/ 01 октября 2008

Причиной использования контейнера DI является то, что вам не нужно предварительно настраивать в своем коде миллиард свойств, которые являются просто геттерами и сеттерами. Вы действительно хотите жестко закодировать всех тех, кто с новым X ()? Конечно, у вас может быть значение по умолчанию, но контейнер DI позволяет создавать синглтоны, что чрезвычайно просто и позволяет вам сосредоточиться на деталях кода, а не на другой задаче его инициализации.

Например, Spring позволяет вам реализовать интерфейс InitializingBean и добавить метод afterPropertiesSet (вы также можете указать «init-метод», чтобы избежать связывания вашего кода с Spring). Эти методы позволят вам убедиться, что любой интерфейс, указанный в качестве поля в экземпляре вашего класса, настроен правильно при запуске, и вам больше не придется проверять нулевые значения ваших получателей и установщиков (при условии, что вы позволяете своим синглетам оставаться поточно-ориентированными). ).

Кроме того, гораздо проще выполнять сложные инициализации с помощью контейнера DI, чем выполнять их самостоятельно. Например, я помогаю с использованием XFire (не CeltiXFire, мы используем только Java 1.4). Приложение использовало Spring, но, к сожалению, оно использовало механизм конфигурации XFire services.xml. Когда коллекция элементов должна была объявить, что у нее НОЛЬ или более экземпляров вместо ОДНОГО или более экземпляров, мне пришлось переопределить часть предоставленного кода XFire для этой конкретной службы.

Есть определенные значения по умолчанию XFire, определенные в его схеме bean-компонентов Spring. Итак, если бы мы использовали Spring для настройки сервисов, bean-компоненты могли бы быть использованы. Вместо этого мне пришлось предоставить экземпляр определенного класса в файле services.xml вместо использования bean-компонентов. Для этого мне нужно было предоставить конструктор и настроить ссылки, объявленные в конфигурации XFire. Реальное изменение, которое мне нужно было сделать, потребовало перегрузки одного класса.

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

<bean id="base" parent="RootXFireBean">
    <property name="secondProperty" ref="secondBean" />
</bean>

<bean id="secondBean" parent="secondaryXFireBean">
    <property name="firstProperty" ref="thirdBean" />
</bean>

<bean id="thirdBean" parent="thirdXFireBean">
    <property name="secondProperty" ref="myNewBean" />
</bean>

<bean id="myNewBean" class="WowItsActuallyTheCodeThatChanged" />

Вместо этого это выглядело так:

public class TheFirstPointlessClass extends SomeXFireClass {
    public TheFirstPointlessClass() {
        setFirstProperty(new TheSecondPointlessClass());
        setSecondProperty(new TheThingThatWasHereBefore());
    }
}

public class TheSecondPointlessClass extends YetAnotherXFireClass {
    public TheSecondPointlessClass() {
        setFirstProperty(TheThirdPointlessClass());
    }
}

public class TheThirdPointlessClass extends GeeAnotherXFireClass {
    public TheThirdPointlessClass() {
        setFirstProperty(new AnotherThingThatWasHereBefore());
        setSecondProperty(new WowItsActuallyTheCodeThatChanged());
    }
}

public class WowItsActuallyTheCodeThatChanged extends TheXFireClassIActuallyCareAbout {
    public WowItsActuallyTheCodeThatChanged() {
    }

    public overrideTheMethod(Object[] arguments) {
        //Do overridden stuff
    }
}

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

2 голосов
/ 25 сентября 2008

Вы можете вставить новую реализацию для подруги. Таким образом, новая самка может быть введена без перекомпиляции вашего кода.

<bean id="jane" class="foo.bar.HotFemale">
  <property name="age" value="19"/>
</bean>
<bean id="mary" class="foo.bar.Female">
  <property name="age" value="23"/>
</bean>
<bean id="john" class="foo.bar.Male">
  <property name="girlfriend" ref="jane"/>
</bean>

(Выше предполагается, что Female и HotFemale реализуют один и тот же интерфейс GirlfFriend)

2 голосов
/ 25 сентября 2008

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

1 голос
/ 06 апреля 2011

С точки зрения весны я могу дать вам два ответа.

Во-первых, конфигурация XML - не единственный способ определить конфигурацию. Большинство вещей можно настроить с помощью аннотаций, а вещи, которые необходимо выполнить с XML, - это конфигурация для кода, который вы все равно не пишете, например, пул соединений, который вы используете из библиотеки. Spring 3 включает в себя метод определения конфигурации DI с использованием Java, аналогичный конфигурации DI, выполненной вручную, в вашем примере. Поэтому использование Spring не означает, что вы должны использовать файл конфигурации на основе XML.

Во-вторых, Spring - это намного больше, чем просто DI-фреймворк. Он имеет множество других функций, включая управление транзакциями и АОП. Конфигурация Spring XML объединяет все эти концепции. Часто в одном и том же файле конфигурации я указываю зависимости bean-компонентов, параметры транзакций и добавляю bean-объекты области действия, которые фактически обрабатываются с использованием AOP в фоновом режиме. Я считаю, что конфигурация XML обеспечивает лучшее место для управления всеми этими функциями. Я также чувствую, что конфигурация на основе аннотаций и конфигурация XML масштабируются лучше, чем конфигурация на основе Java.

Но я понимаю вашу точку зрения, и в определении конфигурации внедрения зависимостей в Java нет ничего плохого. Обычно я делаю это сам в модульных тестах и ​​когда я работаю над проектом, достаточно маленьким, чтобы я не добавил DI-фреймворк. Обычно я не задаю конфигурацию в Java, потому что для меня это тот код, который я стараюсь избегать при написании, когда выбрал использование Spring. Это предпочтение, однако, это не означает, что конфигурация XML превосходит конфигурацию на основе Java.

1 голос
/ 18 июня 2010

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

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

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