Главная страница   /   1.4. Область применения DI (Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Марк Симан

1.4. Область применения DI

Как мы видели в разделе "Hello DI", важный элемент механизма внедрения зависимостей – это вынесение различных ответственностей в отдельные классы. Одна из ответственностей, которую мы выносим в классы – это задача создания экземпляров зависимостей.

Как только класс уступает контроль над зависимостями, он отказывается от решения выбирать конкретные реализации. Тем не менее, как разработчики, мы приобретаем некоторые преимущества.

Примечание

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

Поначалу может показаться недостатком позволять классу отказываться от контроля над тем, какие объекты создаются, но, как разработчики, мы не теряем этот контроль – мы всего лишь переносим его в другое место.

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

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

Благодаря механизму внедрения зависимостей мы можем компоновать приложения во время перехватывания зависимостей и контролирования их жизненного цикла. Композиция объектов, механизм перехвата и управление жизненным циклом – это три аспекта механизма внедрения зависимостей. Далее я вкратце раскрою эти аспекты; более детальное описание приводится в части 3 этой книги.

Композиция объектов

Чтобы воспользоваться результатами таких преимуществ, как расширяемость, позднее связывание и параллельная разработка, мы должны уметь компоновать классы в приложениях (см. рисунок 1-13). Такая композиция объектов часто является самой главной мотивацией для введения в приложение механизма внедрения зависимостей. Первоначально механизм DI был синонимичен композиции объектов; это единственный аспект, обсуждаемый в оригинальной статье Мартина Фаулера относительно этого вопроса.

Рисунок 1-13: Композиция объектов означает, что модули в приложениях можно компоновать.

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

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

Жизненный цикл объектов

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

В .NET о большинстве из этих вещей заботится за нас Garbage Collector. Пользователь может иметь внедренные в класс зависимости и использовать их настолько долго, насколько того требует класс. Когда это происходит, зависимости выходят за рамки области применения механизма DI. Если на эти зависимости не ссылается больше ни один класс, то они как раз подходят для "сборки мусора" (garbage collection).

Что если два пользователя используют один и тот же вид зависимостей? Рисунок 1-14 иллюстрирует тот факт, что мы можем выбрать вариант внедрения отдельного экземпляра для каждого пользователя, тогда как рисунок 1-15 демонстрирует, что мы можем наоборот выбрать вариант, при котором единственный экземпляр будет совместно использоваться несколькими пользователями. Тем не менее, с точки зрения пользователей между этими вариантами нет никакой разницы. Согласно принципу замещения Лисков пользователь должен одинаково воспринимать все экземпляры данного интерфейса.

Рисунок 1-14: Для каждого из пользователей, совместно использующих один и тот же тип зависимостей, внедряется свой собственный приватный экземпляр.
Рисунок 1-15: индивидуальных пользователей, совместно использующих один и тот же тип зависимостей, внедряется один и тот же экземпляр.

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

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

Отказ от контроля над зависимостью также означает отказ от контроля над ее жизненным циклом; нечто, находящееся выше в стеке вызовов, должно управлять жизненным циклом зависимости.

Перехват

Когда мы делегируем контроль над зависимостями стороннему компоненту, как это демонстрирует рисунок 1-16, мы также приобретаем возможность модифицировать эти зависимости до того, как мы передадим их в классы, которые используют эти зависимости.

Рисунок 1-16: Вместо того, чтобы внедрять первоначально запланированные зависимости, мы можем модифицировать эти зависимости посредством упаковывания их в другой класс до того, как передать их пользователю. Пунктирная стрелка указывает на направление действия – направление зависимости идет в обратном направлении.

В примере "Hello DI" я первоначально внедрял экземпляр ConsoleMessageWriter в класс Salutation. Затем, модифицируя пример, я добавил возможность обеспечения безопасности путем создания нового SecureMessageWriter, который всего лишь делегирует дальнейшую работу ConsoleMessageWriter при аутентификации пользователя. Это позволяет нам поддерживать принцип единственной ответственности.

Это возможно сделать, потому что мы всегда программируем на основании интерфейсов; помните, что эти зависимости всегда должны быть абстракциями. Что касается класса Salutation, то он не заботится о том, является ли используемый IMessageWriter экземпляром ConsoleMessageWriter или SecureMessageWriter. SecureMessageWriter может упаковывать ConsoleMessageWriter, который, однако, выполняет реальную работу.

Примечание

Перехват – это применение паттерна проектирования Decorator. Не переживайте, если вам не знаком паттерн проектирования Decorator – в главе 9 я предоставлю вам повторный курс, который всецело посвящен механизму перехвата.

Такие возможности перехвата переносят нас прямиком к аспектно-ориентированному программированию (Aspect-Oriented Programming) – тесно связанная с механизмом внедрения зависимостей тема, которая, тем не менее, выходит за пределы этой книги. Благодаря механизму перехвата мы можем применять такие сквозные механизмы, как авторизация, контрольные проверки, управление доступом, валидация и т.д. в хорошо структурированной манере, которая позволяет нам поддерживать концепцию разделения.

Механизм DI с точки зрения трех аспектов

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

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

В части 3 я посвятил каждую главу отдельному аспекту, но в данной главе я предоставил обзор этих аспектов, поскольку важно знать, что на практике механизм внедрения зависимостей – это больше, чем просто композиция объектов.