Рамки внедрения зависимостей: как они работают?

Я считаю себя опытным программистом и понимаю основную концепцию инъекции зависимостей. С другой стороны, большая часть моего опыта заключается в написании относительно низкоуровневого кода с одним человеком. У меня нет опыта работы над проектами крупных предприятий.

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

Изменить: здесь я получил отличные ответы. Правильно ли я говорю, что структура DI в основном дает вам удобный способ создания глобально доступных фабрик, которые создают/возвращают экземпляры зависимостей всякий раз, когда объект запрашивает их? Если это так, я делал такие вещи очень часто в моем коде, но никогда не думал использовать для этого какую-либо формальную/тяжеловесную фреймворк.

Ответы

Ответ 1

DI - все о развязывающих зависимостях между классами.

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

DI также не обязательно является глобальным или широко распространенным делом; его можно настроить различные схемы зависимостей для одного и того же приложения и тех же классов. Там могут быть даже зависимости от времени выполнения. DI может даже определить жизненный цикл или область, в которой должны жить объекты (область запроса, область сеанса и т.д.).

Он также не тяжелый или назойливый, если вы посмотрите на облегченные реализации DI, такие как Googles Guice. Не зря они называют это новым "новым".

Ответ 2

В Википедии есть хорошая запись о преимуществах и реализациях.

http://en.wikipedia.org/wiki/Inversion_of_control

Я считаю, что контейнеры IoC, такие как Spring, могут быть полезны из-за всех других "вещей", которые они выполняют, таких как управление транзакциями и кода, который они предоставляют, например, шаблонов кода, тестирования базовых классов и т.д., которые чрезвычайно полезны. Кроме того, в мире Java я считаю, что IoC является реакцией на стандарт Java EE, который многие разработчики считают слишком неуклюжим. IoC, так как он реализован с помощью Spring, позволяет вам разрабатывать компоненты службы POJO, по большей части, которые, я думаю, проще, чем обручи, которые Java EE заставляли людей перепрыгивать.

Я много из этого, или, по крайней мере, некоторые из них, - это умственная мастурбация, до некоторой степени, то есть преимущества IoC, предоставляемые контейнерами, не полностью используются в большинстве проектов, которые их используют.
"Мы должны использовать DI, потому что мы сможем легко заменить реализации наших DAO в будущем, yippeeeeee" - это редко случается в моем опыте. Люди используют их, потому что они стали стандартом и модным словом IMHO. Я не говорю, что это плохие вещи; просто, что часть IoC стандартного программного обеспечения на самом деле не является причиной их использования.

Ответ 3

Мне очень нравится ответ @hvgotcodes, но мысль id добавляет несколько точек, как im большой поклонник DI/IoC.

Мы используем DI для следующих преимуществ:

  • Тестируемость. Мы пишем модульные тесты против репозитория (например) через интерфейсы. Теперь для тестирования интеграции мы используем DI для ввода "реального" репозитория для наших тестов. Но как насчет модульного тестирования? Мы не хотим тестировать "реальный" репозиторий/базу данных. Поэтому мы используем DI для "инъекции" ложного репозитория. Многое связано с магией интерфейсов (конечно, вы могли бы "жестко программировать" ваши тесты, чтобы использовать поддельный репозиторий, когда это необходимо).

  • Центральная точка инъекции. - Реестр AKA. Каждый проект/модуль имеет реестр, в который мы вставляем компоненты. Например, в нашем тестовом проекте, когда "что-то" запрашивает IRepository, мы вводим MockRepository. В нашем проекте уровня обслуживания, когда "что-то" запрашивает IRepository, мы добавляем EntityFrameworkRepository.

  • Отличное управление цветом. Это зависит от контейнера DI (мы используем StructureMap для .NET). Но при настройке реестра вы можете настроить свои объекты на время жизни Singleton, HTTP-Context, ThreadLocal в одной строке. Это снова приятно, потому что вы обрабатываете объекты в центральном месте. Ваши компоненты не имеют представления об их жизни, они могут сосредоточиться на своей работе и ничего больше.

  • Однострочное переключение компонентов. Это стало возможным благодаря реестру (пункт № 2). Но на данный момент мы используем Entity Framework для настойчивости. Единственное, что "знает" об этом, - это реестр. Поэтому, если в один прекрасный день выйдет что-то лучшее, все, что нам нужно сделать, это создать еще одну реализацию IRepository и щелкнуть по одной строке в реестре. Это очень возможно при длительном применении. Мы использовали L2SQL для v1 нашего сайта, теперь мы используем EF для v2. Если бы мы использовали DI с v1, время нарезания было бы почти вдвое. Но все привязано к уровню персистентности, поэтому мы в основном переписываем сайт с нуля.

Преимущества DI/IoC действительно видны, когда вы объединяете множество разных принципов, таких как репозиторий, POCO, DDD, интерфейсы, N-уровень.

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

Что мои два цента.

Ответ 4

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

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

Основы довольно легки на любом языке, который имеет какую-то интроспекцию (зарегистрируйте конкретный тип, чтобы удовлетворить интерфейс, когда вам нужен экземпляр интерфейса, а затем создайте экземпляр типа. Пройдите график, созданный конструктором, и повторите.), где он становится сложным, - это когда вы также хотите контролировать время жизни объекта (этот класс Logger должен быть создан только один раз, а затем повторно использован. Этот DataAccessFacade должен быть создан один раз на поток и т.д.) или динамически выбирать, как удовлетворить зависимость. Рамка DI обычно предоставляет как объекты создания объектов (выяснить зависимости, необходимые для конструктора), так и средство локализации службы, чтобы в конструкторе или в качестве параметра методов не было передано все. Это позволяет вам регистрировать типы с произвольно сложными конструкторами, получать экземпляр из него и не беспокоиться о том, когда и как делиться экземпляром, когда его создавали, когда его уничтожали или связывали с фактическим типом.

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