Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
Скачать 5.66 Mb.
|
1 2 Аннотация: Внедрение зависимостей позволяет уменьшить сильное связывание между программными компонентами. Вместо жесткого кодирования зависимостей (например, драйвера какой- либо базы данных), внедряется список сервисов, в которых может нуждаться компонент. После этого сервисы подключаются третьей стороной. Такой подход обеспечивает лучшее управление будущими изменениями и решение проблем в разрабатываемом программном обеспечении. Данная книга рассказывает о внедрении зависимостей и является практическим руководством по их применению в приложениях .NET . Издание содержит основные шаблоны внедрения зависимостей, написанные на «чистом» C#. Кроме того, рассмотрены способы интеграции внедрений зависимостей со стандартными технологиями Microsoft, такими как ASP.NET MVC, а также примеры применения фреймворков StructureMap, Castle Windsor и Unity. 3 Книга "Внедрение зависимостей в .NET " демонстрирует основные DI паттерны на обычном языке C#, поэтому вы в полной мере осознаете, как работает механизм внедрения зависимостей, кроме того она охватывает интеграцию с такими стандартными технологиями компании Microsoft, как ASP.NET MVC, и учит вас, как использовать такие DI фреймворки, как Structure Map, Castle W indsor и Unity. Внедрение зависимостей – это прекрасный способ уменьшить сильное связывание между компонентами программного обеспечения. Вместо жесткого кодирования зависимостей, например, указания драйвера базы данных, вы внедряете перечень сервисов, которые могут понадобиться компоненту. Затем сервисы соединяются при помощи стороннего компонента. Эта методика позволяет вам лучшим образом справляться с будущими изменениями и другими возникающими в вашем программном обеспечении сложностями. Книга "Внедрение зависимостей в .NET " знакомит вас с механизмом внедрения зависимостей и предоставляет практическое руководство по применению этого механизма .NET приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей. Далее вы научитесь интегрировать DI с такими стандартными технологиями компании Microsoft, как ASP.NET MVC, и использовать такие DI фреймворки, как Structure Map, Castle W indsor и Unity. К концу книги вы будете свободно применять эту мощную методику в рамках повседневной разработки .NET приложений. Эта книга написана для разработчиков на языке C#. Не требуется никакого первоначального опыта работы с механизмом внедрения зависимостей или DI фреймворками. Ч то внутри: Множество примеров на языке C# Каталог DI паттернов и анти-паттернов Примеры использования как фреймворков компании Microsoft, так и DI фреймворков с открытым исходным кодом Появление механизма внедрения зависимостей Каталог DI DI самостоятельно DI-контейнеры 4 По явление механизма внедрения зависимостей Механизм внедрения зависимостей (Dependency Injection или DI) принадлежит к списку самых неправильно воспринимаемых концепций объектно-ориентированного программирования. Эта путаница широко распространена и касается терминологии, целей и механики. Должен ли этот механизм называться внедрением зависимостей, инверсией управления (Inversion of Control) или даже сторонним подключением (T hird-Party Connect)? Является ли целью DI всего лишь поддержка модульного тестирования или же имеется более широкая цель? DI – это тоже самое, что и Service Locator? Нужен ли DI- контейнер? Существует множество публикаций блогов, журнальных статей, конференционных презентаций и т.д., в которых обсуждается механизм DI, но, к несчастью, во многих из них используется противоречивая терминология и даются неправильные советы. Это справедливо и за рамками указанных источников, и даже такие крупные и влиятельные деятели, как компания Microsoft, тоже вовлечены в эту путаницу. Дело не должно обстоять таким образом. В этой книге я представляю и использую непротиворечивую терминологию, которую, я надеюсь, примут и другие. В большинстве случаев я принимал и разъяснял существующую терминологию, определенную другими, но временами я добавлял некоторую терминологию, которая ранее не существовала. Это чрезвычайно помогло мне в выделении спецификаций области применения и границ DI. Одной из основных причин противоречивости и неправильных советов является тот факт, что границы DI довольны нечеткие. Где заканчивается DI и начинаются другие концепции объектно-ориентированного программирования? Думаю, невозможно провести разграничительную линию между DI и другими аспектами написания качественного объектно-ориентированного кода. При обсуждении DI нам приходится вовлекаться в другие концепции такие, как SOLID и Clean Code. Не думаю, что я могу достоверно писать о механизме DI, не затрагивая при этом некоторых из этих других тематик. Первая часть книги поможет вам осознать позицию DI по отношению к другим аспектам разработки программного обеспечения – заявляя, таким образом, о нем на всеуслышание. Первая глава предоставляет вам быстрый обзор DI, охватывая его цели, принципы и преимущества, а также предоставляя набросок области ее применения для остальной части книги. Если вы хотите узнать, что такое DI и почему этот механизм должен вас заинтересовать, то это как раз то, с чего вам следует начать. Содержание главы предполагает, что у вас нет никаких первичных познаний DI, но даже если вы уже знали о ней, вы все еще можете иметь желание прочитать ее – может оказаться, что ее содержание отлично от того, что вы ожидали. Первая глава сфокусирована на общей картине представления и не вдается в детали. Глава 2, с другой стороны, всецело зарезервирована под крупный пример. Подразумевается, что этот пример даст вам намного больше конкретного понимания DI. Он разделен на две части и сформирован практически в виде комментария. Для того чтобы противопоставить DI другим "традиционным" стилям программирования, глава сначала демонстрирует типичную, сильно связанную реализацию шаблонного приложения, а впоследствии заново реализует его с помощью DI. 5 Т ретья и конечная глава части 1 вводит понятие DI-контейнера и объясняет, как оно вписывается в общую картину представления DI. Я обсуждаю DI в общих понятиях и, несмотря на то, что я предоставляю примеры кода, которые демонстрируют, как работает типичный DI-контейнер, целью главы не является объяснение деталей конкретного API. Главная цель главы 3 – показать, что DI-контейнер является довольно полезным, необязательным инструментом. Вполне допустимо использовать DI без использования DI- контейнера, поэтому части 2 и 3 более или менее игнорируют DI-контейнеры и вместо этого обсуждают DI, не затрагивая контейнеры. Далее в части 4 мы возвращаемся к понятию DI-контейнера с целью анализа четырех конкретных контейнеров. Часть 1 определяет контекст всей остальной книги. Она нацелена на читателей, которые не имеют первичных познаний DI, но опытные специалисты, использующие DI, могут также приобрести полезные знания, просматривая главы с целью получить понимание терминологии, используемой в рамках всей книги. К концу части 1 вы должны приобрести прочное понимание словаря и общих понятий, даже если некоторые конкретные детали все еще слегка расплывчаты. Ничего страшного – книга становится более конкретной на протяжении ее чтения, поэтому части 2, 3 и 4 должны будут ответить на вопросы, которые, скорее всего, появятся у вас после прочтения части 1. 1. "Дегустационное меню" механизма внедрения зависимостей 2. Комплексный пример 3. DI-контейнеры 6 1. "Дегустационное меню" механизма внедрения зависимостей Меню: Неправильное понимание механизма внедрения зависимостей Цель механизма внедрения зависимостей Преимущества механизма внедрения зависимостей Когда необходимо применять механизм внедрения зависимостей Вы могли слышать о том, что процесс приготовления беарнского соуса (sauce béarnaise) достаточно сложен. Даже большинство из тех людей, которые регулярно занимаются приготовлением пищи, никогда не пытались его приготовить. Это позорно, поскольку соус очень вкусный (его традиционно подают вместе с бифштексом, но он также отлично сочетается с белой спаржей, яйцами-пашот и другими блюдами). Некоторые прибегают к таким его заменителям, как готовые соусы или растворимые смеси, но нет ничего более восхитительного истинного беарнского соуса. О пределение. Беарнский соус – это эмульсионный соус, приготовленный из яичного желтка и масла и приправленный эстрагоном, кервелем, луком-шалотом и уксусом. Этот соус не содержит воды. Самой большой проблемой при приготовлении беарнского соуса является то, что он может и не получиться – соус может загуститься или расслоиться, и если это случится, вы не сможете его восстановить. Процесс приготовления соуса занимает примерно 45 минут, поэтому проваленная попытка означает, что у вас уже не будет времени на вторую попытку. С другой стороны, любой шеф-повар умеет готовить беарнский соус. Это входит в их обучение и, как они скажут вам сами, это не трудно. Вам не нужно быть профессиональным поваром, чтобы приготовить этот соус. Всякий, кто учился его готовить, по крайней мере, один раз терпел неудачу, но как только вы научитесь его готовить, у вас он будет получаться всегда. Я думаю, что механизм внедрения зависимостей – это нечто вроде беарнского соуса. Полагают, что он сложен, и поэтому немногие его применяют. Если вы попытаетесь его использовать и потерпите неудачу, то у вас, скорее всего, не будет времени на вторую попытку. О пределение. Механизм внедрения зависимостей (Dependency Injection) – это набор принципов и шаблонов проектирования программного обеспечения, которые дают нам возможность разрабатывать слабосвязанный код. Несмотря на Страх, Неопределенность и Сомнения (FUD), окружающие DI, ему так же легко научиться, как и приготовлению беарнского соуса. Вы можете допускать ошибки во 7 время обучения, но как только вы овладеете методикой, вы уже больше никогда не потерпите неудачу при применении этого механизма. В разделе вопросов и ответов, связанных с разработкой программного обеспечения, веб- сайта Stack Overflow демонстрируется ответ на вопрос "Как объяснить механизм внедрения зависимостей пятилетнему ребенку?". Самый высоко оцененный ответ, который был дан Джоном Маншем, предоставляет поразительно точную аналогию, нацеленную на (воображаемого) пятилетнего изыскателя: Когда ты идешь к холодильнику и что-то самост оя тельно дост аеш ь из него, т ы может е стат ь причиной возникновения проблем. Ты можешь ост авит ь дверь от крыт ой, т ы можешь взять то, чт о мама или папа не хот ели бы, чтобы ты брал. Ты даже можеш ь искат ь т о, чего у нас и не было, или т о, чт о уже закончилось. Все, чт о т ебе следует сделат ь, – это сформулироват ь, чт о т ебе нужно, "Мне нужно то-то, чт обы выпить во время ланча", и после этого мы будем следит ь за т ем, чт обы у тебя было эт о, когда т ы садиш ься кушат ь. В терминах разработки программного обеспечения с использованием технологий объектно-ориентированного программирования это означает: взаимодейству ющие классы (пятилетние дети) должны полагаться на инфраструктуру (родителей), которая предоставляет необходимые услуги. Как продемонстрировано на рисунке 1-1, данная глава совершенно прямолинейна по своей структуре. Сначала я знакомлю с DI, в том числе с его целями и преимуществами. Несмотря на то, что я включаю в нее примеры, в целом в этой главе содержится меньше кода, нежели в любой другой главе этой книги. Рисунок 1-1: Структура книги совершенно прямолинейна. Вы должны прочитать первый раздел перед тем, как прочитать следующий и т.д. Это может показаться очевидным, но некоторые из последующих глав этой книги менее прямолинейны по своей натуре. 8 Перед тем как познакомить вас с механизмом внедрения зависимостей, я буду обсуждать основную цель DI: удобство сопровождения. Это важно, поскольку очень легко неправильно понять механизм DI, если вы не подготовлены должным образом. Далее, после примера (Hello DI), я буду рассматривать преимущества и область применения DI, в сущности, прокладывая маршрутную карту этой книги. Когда вы закончите изучение этой главы, вы будете подготовлены к более продвинутым концепциям, обсуждаемым в остальной части книги. Для большинства разработчиков механизм внедрения зависимостей может казаться слегка отсталым способом создания исходного кода и, по аналогии с беарнским соусом, в него включено слишком много Страха, Неопределенности и Сомнения. Для того чтобы изучить механизм внедрения зависимостей, вам для начала необходимо понять его цель. 1.1. Написание поддерживаемого кода 1.2. Привет DI 1.3. Что внедрять, а что не внедрять 1.4. Область применения DI 1.5. Р езюме 9 1.1. Написание поддерживаемого кода Каким целям служит механизм внедрения зависимостей? DI, сам по себе, не является целью, скорее, это средство достижения результата. В конечном итоге целью большинства технологий программирования является предоставление программного обеспечения, работающего настолько эффективно, насколько это возможно. Одним из аспектов этой цели является написание поддерживаемого кода. За исключением тех моментов, кода вы будете писать прототипы или приложения, у которых никогда не будет релиза выше первого, вы вскоре обнаружите, что занимаетесь поддержанием и расширением существующего кода. Для того чтобы с таким кодом можно было эффективно работать, он должен быть настолько поддерживаемым, насколько это возможно. Одним из множества способов создания поддерживаемого кода является использование слабого связывания. Еще в 1995 году, когда "Банда четырех" (Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидс) написала книгу "Паттерны проектирования" ("Design Patterns"), существовало универсальное знание: Программируй, основываясь на интерфейсе, а не на классах. Этот совет является не заключением, а скорее остроумной предпосылко й книги "Паттерны проектирования": он появляется на странице 18. Слабое связывание делает код расширяемым, а расширяемость делает его поддерживаемым. DI – это не более чем технология, которая разрешает слабое связывание. Как бы то ни было, существует множество моментов недопонимания механизма внедрения зависимостей, и иногда они мешают истинному пониманию. Перед тем как приступить к изучению, вы должны забыть то, что (как вы думаете) вы уже знали. Какие знания о DI нуж но забыть Подобно стереотипу, касающемуся голливудских восточных единоборств, вы должны забыть все, что вы знали раньше, прежде чем сможете чему-то научиться. Существует множество моментов недопонимания механизма DI, и если вы будете носить эти знания в себе, то вы неправильно поймете то, что вы прочитаете в этой книге. Вы должны очистить свой разум, чтобы понять механизм DI. Существует, по крайней мере, четыре общих мифа, касающихся механизма внедрения зависимостей: DI имеет отношение только к "позднему связыванию" (late binding). DI имеет отношение только к модульному тестированию (unit testing). DI – это вид абстрактной фабрики ( Abstract Factory ) на "стероидах". Для механизма DI необходим DI-контейнер. Несмотря на то, что ни один из этих мифов не является правдой, они, тем не менее, широко распространены. Мы должны развеять их до того, как приступим к изучению механизма внедрения зависимостей. П озднее связывание 10 В данном контексте под поздним связыванием понимается возможность заменять части приложения без необходимости перекомпиляции кода. Приложение, которое разрешает добавление-вставку сторонних компонентов (таких, как Visual Studio), является одним из примеров позднего связывания. Еще один пример – стандартное программное обеспечение, которое поддерживает различные исполняемые среды. У вас может быть приложение, которое может запускаться больше, чем на одном движке базы данных: например, такое, которое поддерживает как Oracle, так и SQL Server. Для поддержки такой возможности остальная часть приложения может обращаться к базе данных посредством интерфейса. База кода может предоставлят ь различные реализации этого интерфейса с целью обеспечения доступа к Oracle и SQL Server соответственно. Для контроля над тем, какая реализация должна применяться для данной инсталляции, может использоваться опция конфигурации. Т от факт, что механизм внедрения зависимостей имеет отношение только к такому виду сценариев, является повсеместным заблуждением. Это понятно, поскольку DI разрешает такой сценарий, но ошибочно думать, что зависимость симметрична. Т о, что DI разрешает позднее связывание, еще не означает, что он имеет отношение только к сценариям позднего связывания. Как это продемонстрировано на рисунке 1-2, позднее связывание является всего лишь одним из аспектов механизма внедрения зависимостей. Рисунок 1-2: Позднее связывание разрешено механизмом внедрения зависимостей, но полагать, что DI применим только в сценариях позднего связывания значит принимать ограниченное представление более широкой перспективы. Если вы думали, что механизм внедрения зависимостей имеет отношение только к позднему связыванию, то вам необходимо забыть об этом. М еханизм DI делает намного больше, чем просто разрешает позднее связывание. Модульное тестирование Некоторые люди думают, что механизм внедрения зависимостей имеет отношение только к поддержке модульного тестирования. Это неверно, несмотря на то, что механизм DI определенно является важной составляющей поддержки модульного тестирования. По правде говоря, мое первоначальное знакомство с механизмом DI произошло во время борьбы с определенными аспектами технологии разработ ки через тестирование (Test- Driven Developm ent или TDD). Во время этой борьбы я познакомился с DI и узнал, что остальные использовали этот механизм для того, чтобы поддерживать некоторые сценарии, похожие на те, к которым я обращался. Даже если вы не пишите модульные тесты (если вы этого не делаете, то вам следует начать прямо сейчас), механизм DI все еще уместен, благодаря всем остальным преимуществам, которые он предлагает. Утверждение о том, что механизм DI имеет отношение только к поддержке модульного тестирования, подобно утверждению о том, что DI имеет отношение только к поддержке позднего связывания. Рисунок 1-3 11 демонстрирует, что, несмотря на то, что это уже другое представление, это представление такое же ограниченное, как и изображенное на рисунке 1-2. В данной книге я сделал все возможное, чтобы продемонстрировать вам общую картину. Рисунок 1-3: Хотя предположение о том, что модульное тестирование является единственной целью механизма DI, – это уже другое представление, нежели то, которое касалось позднего связывания, оно все равно является ограниченным представлением более широкой перспективы. Если вы думали, что механизм внедрения зависимостей имеет отношение только к модульному тестированию – забудьте об этом. Механизм DI делает намного больше, чем просто разрешает модульное тестирование. Абстрактная фабрика "на стероидах" Возможно, самым опасным заблуждением является то, что механизм DI включает в себя некоторого рода универсальну ю абстрактную фабрику, которую мы можем использовать для создания экземпляров необходимых нам зависимостей. Во введении к данной главе я писал, что "взаимодейст вующие классы … должны полагаться на инфраструктуру …, которая предоставляет необходимые услуги". Какими были ваши первоначальные соображения по поводу этого предложения? Думали ли вы об инфраструктуре, как о некоторого рода сервисе, к которому вы могли бы обратиться, чтобы получить необходимую зависимость? Если это так, то вы не одиноки в своих мыслях. Многие разработчики и архитекторы думают о механизме внедрения зависимостей как о сервисе, который может использоваться для указания местоположения других сервисов; этот сервис имеет название Service Locator (сервис-локатор), но он является полной противоположно стью DI. Если вы думали о механизме DI, как о сервис-локаторе, – а именно, универсальной фабрике – вам нужно об этом забыть. Механизм внедрения зависимостей является противоположност ью Service Locator; это способ структурирования кода таким образом, чтобы нам никогда не нужно было обязательно запрашивать зависимости. В противном случае мы принуждаем пользователей возмещать их. DI-конте йнеры Т есно связано с предыдущим заблуждением мнение о том, что для механизма DI необходим DI-контейнер. Если бы вы придерживались предыдущего, ошибочного мнения о том, что DI включает в себя Service Locator, то было бы легко прийти к выводу о том, что DI-контейнер может взять на себя ответственность за Service Locator. Это имеет место быть, но это никак не является тем, как мы должны использовать DI-контейнер. DI-контейнер – это необязательная библиотека, которая может упростить процесс создания компонентов при регистрации приложения, но это не обязательный способ. 12 Когда мы создаем приложения без использования DI-контейнера, мы называем это Poor m an's DI; для этого требуется больше работы, но несколько другого рода, при которой нам не нужно идти на компромисс при использовании какого-либо принципа механизма внедрения зависимостей. Если вы думали, что для механизма DI нужен DI-контейнер, то забудьте об этом. Механизм внедрения зависимостей – это набор принципов и паттернов, а DI-контейнер – это полезный, но необязательный инструмент. Вы можете думать, что, хотя я и изложил четыре мифа о механизме внедрения зависимостей, мне, тем не менее, нужно предоставить неопровержимое доказательство против каждого из этих мифов. Это верно. В некотором смысле, вся эта книга является большим аргументом против этих общих заблуждений. По моему мнению, забывание некоторых фактов жизненно необходимо, поскольку люди имеют склонность к тому, чтобы пытаться модифицировать те факты, которые я рассказываю им о механизме DI и совмещать это с тем, что, по их мнению, они уже знали о механизме внедрения зависимостей. Когда такое случается, много времени тратится прежде, чем они, в конце концов, осознают, что некоторые из их основных предпосыло к не верны. Я хочу поделиться с вами этими знаниями. Поэтому, если вы сможете, попытайтесь прочитать эту книгу так, будто вы ничего не знаете о механизме DI. Давайте предположим, что вы ничего не знаете о механизме внедрения зависимостей и о его целях и начнем с того, что рассмотрим то, что механизм DI делает. Осознание цели DI Механизм DI – это не конечная цель, это средство достижения результата. Механизм внедрения зависимостей разрешает слабое связывание, а слабое связывание делает код более поддерживаемым. Это совершенное утверждение, и, несмотря на то, что я мог бы отослать вас за подробностями к таким прочно установившимся авторитетам, как "Банда четырех", я думаю, что справедливо будет объяснить вам, почему это именно так. Разработка программного обеспечения все еще остается довольно новой профессионально й сферой, поэтому в большинстве случаев мы все еще находимся в процессе разгадывания того, как реализовать хорошую архитектуру. Тем не менее, некоторые личности, имеющие опыт в более традиционных профессиях (например, конструирование), давно это разгадали. П роверка в дешевом отеле Если вы останавливаетесь в дешевом отеле, то можете столкнуться со зрелищем, подобно тому, которое продемонстрировано на рисунке 1-4. На этом рисунке продемонстрирован фен, дружелюбно предоставленный отелем для вашего удобства, но, по-видимому, администрация отеля не верит, что вы оставите фен следующему гостю: прибор напрямую присоединен к стенной розетке. Несмотря на то, что шнур достаточно длинный для того, чтобы предоставить вам определенную степень подвижности, вы не можете взять фен с собой. По-видимому, управление отеля решило, что стоимость замены украденных фенов достаточно высока и оправдывает то, что в противном случае явно является худшей реализацией. 13 Рисунок 1-4: В комнате дешевого отеля вы можете найти фен, подсоединенный к стенной розетке напрямую. Это эквивалентно использованию универсальной практики написания сильно связанного кода. Что происходит, когда фены прекращают работать? Отелю приходится вызывать квалифициро ванного профессионала, который умеет решать эту проблему. Для ремонта жестко встроенного фена им придется отключить питание во всей комнате, делая ее при этом временно бесполезной. Затем специалист будет использовать специальные инструменты для того, чтобы тщательно отсоединить фен и заменить его на новый. Если вам повезет, то специалист не забудет снова включить питание в комнате и проверить, работает ли новый фен… Если вам повезет. Знакома ли вам вообще эта процедура? Т ак вы бы приступали к работе с сильно связанным кодом. В этом сценарии фен сильно связан со стеной, и вы не сможете с легкостью модифицировать одного без влияния на другого. С равнение электрической проводки с паттернами прое ктирования Обычно мы не монтируем электрические устройства вместе, напрямую присоединяя кабель к стене. Вместо этого, как продемонстрировано на рисунке 1-5, мы используем вилки и розетки. Розетка определяет форму, к которой необходимо присоединить вилку. Если провести аналогию с проектированием программного обеспечения, то розетка – это интерфейс. 14 Рисунок 1-5: Посредством использования розеток и вилок фен можно слабо связать со стенной розеткой. В противоположност ь жестко встроенному фену вилки и розетки определяют слабо связанную модель соединения электрических устройств. Поскольку вилка вставляется в розетку, мы можем комбинировать устройства различными способами. Что в особенности интересно, так это то, что многие из этих универсальных комбинаций можно сравнить с хорошо известными принципами и паттернами проектирования программного обеспечения. Во-первых, мы больше не стеснены рамками фенов. Если вы самый обычный читатель, то я бы предположил, что вам больше нужен компьютер, чем фен. Это не проблема: мы выдернем из розетки фен и подключи м компьютер к той же розетке, как это показано на рисунке 1-6. Рисунок 1-6: Используя розетки и вилки, мы можем заменить первоначально используемый фен из рисунка 1-5 на компьютер. Это соответствует принципу замещения Лисков. 15 Удивительно, что понятие розетки стало использоваться на десятилетия раньше, чем появились компьютеры, и до сих пор она также является существенным для компьютера прибором. Первоначальные разработчики розеток, вероятно, не могли предсказать появление персональных компьютеров, но, поскольку их конструкция такая универсальная, можно столкнуться с потребностями, которые первоначально не рассматривались. Возможность замены одной конечной детали без смены другой схожа с центральным принципом проектирования программного обеспечения, который носит название принцип замещения Лисков. Этот принцип утверждает, что мы могли бы заменить одну реализацию интерфейса на другую, не разрушив при этом ни клиента, ни реализацию. Что касается механизма внедрения зависимостей, принцип замещения Лисков – это один из самых важных принципов проектирования программного обеспечения. Это принцип, который дает нам возможность обращаться к потребностям, которые возникнут в будущем, даже если мы не можем предвидеть их сегодня. Как это проиллюстрировано на рисунке 1-7, мы можем выдернуть шнур компьютера из розетки, если на данный момент нам не нужно его использовать. Даже если ничего не подключено к розетке, стена не взрывается. Рисунок 1-7: Отключение компьютера не приводит ни к взрыву стены, ни к взрыву компьютера. Это можно приближенно сравнить с паттерном Null O bject. Если мы отсоединим компьютер от стены, то ни стена, ни компьютер не разрушатся (в действительности, если это портативная ЭВМ, то она может работать и на собственных 16 батарейках в течение некоторого времени). Тем не менее, что касается программного обеспечения, клиент часто ожидает, что сервис будет доступен. Если сервис был удален, то мы получаем NullReferenceException . Для того чтобы справиться с этой ситуацией, мы можем создать реализацию интерфейса, которая ничего не делает. Это паттерн проектирования, известный как Null O bject, и он приблизительно соответствует отсоединению компьютера от стены. Благодаря тому, что мы используем слабое связывание, мы можем заменить существующу ю реализацию чем-то таким, что ничего не выполняет и при этом не приводит к проблемам. Существует множество других вещей, которые мы можем сделать. Если мы живем по соседству со скачкообразным отключением электричества, то мы можем захотеть, чтобы наш компьютер продолжал работать и после отключения питания, подключив его для этого к системе бесперебойного питания (Uninterrupted Power Supply), как это продемонстрировано на рисунке 1-8: мы подсоединяем систему бесперебойного питания к стенной розетке, а компьютер к этой системе. Рисунок 1-8: Можно воспользоваться системой бесперебойного питания для того, чтобы компьютер продолжал работать при отключении электричества. Это соответствует паттерну проектирования Decorator. Компьютер и система бесперебойного питания служат разным целям. Каждый из них обладает самостоятельной ответственностью, на которую не может посягнуть другое устройство. Скорее всего, система бесперебойного питания и компьютер производятся двумя разными производителями, покупаются в разное время и подключаются в разное время. Как демонстрирует рисунок 1-6, мы можем запустить компьютер без системы бесперебойного питания (СБП), но мы также могли бы, возможно, использовать фен во время отключения электричества путем подключения его к СБП. В проектировании программного обеспечения этот способ пересечения реализации другой реализацией того же самого интерфейса известен как паттерн проектирования Decorator. Он предоставляет нам возможность последовательно вводить новые возможности и сквозные сущности без необходимости заново переписывать и изменять огромные объемы существующего кода. Еще один способ добавления новой функциональност и в существующий код – комбинировать существующу ю реализацию интерфейса с новой реализацией. Когда мы соединяем несколько реализаций в одну, мы используем паттерн проектирования C om posite. Рисунок 1-9 иллюстрирует то, как это соответствует подключению различных устройств к удлинителю. 17 Рисунок 1-9: Удлините ль дает возможность подключать несколько устройств к одной стенной розетке. Это соответствует паттерну проектирования Composite. Удлинитель имеет единственную вилку, которую мы можем вставить в единственную розетку, тогда как сам удлинитель предоставляет несколько розеток для множества устройств. Это позволяет нам подключать и отключать фен во время работы компьютера. Аналогично паттерн C om posite облегчает процесс добавления и удаления функциональности посредством модификации набора составных реализаций интерфейса. Ниже приведен окончательный пример. Мы иногда обнаруживаем, что вилка не подходит к определенной розетке. Если вы путешествовали в другую страну, то вы, скорее всего, замечали, что розетки во всем мире отличаются друг от друга. Если вы во время путешествия возите с собой что-то, например фотоаппарат, как показано на рисунке 1-10, то вам нужен адаптер для того, чтобы заряжать его. Соответственно, это паттерн проектирования с таким же именем – Adapter. Рисунок 1-10: Во время путешествия нам часто нужно использовать адаптер для того, чтобы подключить устройство к иностранной розетке (например, чтобы перезарядить фотоаппарат). Это соответствует паттерну проектирования Adapter. Паттерн проектирования Adapter работает так же, как и его физический тезка. Он может использоваться для соединения двух связанных, но разделенных интерфейсов друг с другом. Это в особенности полезно, когда у вас есть существующее стороннее API, которое вы хотите использовать в качестве экземпляра интерфейса, используемого вашим приложением. 18 По отношению к модели розетки и вилки удивительным является тот факт, что на протяжении десятилетий она является простой и универсальной моделью, и это доказано. После создания инфраструктура может использоваться кем угодно и адаптироваться к изменениям потребностей и непредсказуемым требованиям. Что еще более интересно, так это то, что когда мы связываем эту модель с разработкой программного обеспечения, все строительные блоки уже находятся на своих местах в виде принципов и паттернов проектирования. Слабое связывание может сделать код более поддерживаемым. Это самая простая часть. Программировать на основании интерфейса, а не реализации легко. Возникает вопрос, откуда берутся интерфейсы? В некотором смысле, это то, чему посвящена эта книга. Вы можете создать новый экземпляр интерфейса так же, как вы создаете новый экземпляр конкретного типа. Код, подобный продемонстрированному ниже, не компилируется: IMessageWriter writer = new IMessageWriter() IMessageWriter : Программирование на основании интерфейсов new IMessageWriter() : Не компилируется У интерфейса нет конструктора, поэтому это невозможно. Экземпляр writer должен быть создан с помощью другого механизма. Механизм DI решает эту проблему. С таким представлением цели механизма DI, думаю, вы готовы к примеру. 19 1.2. Привет DI В традиции бесчисленных учебников по программированию давайте рассмотрим простое консольное приложение, которое выводит "Hello DI!" на экран. В этом разделе я продемонстрирую вам, на что похож код и вкратце обрисую некоторые ключевые преимущества, не вдаваясь при этом в детали, – в остальной части книги я рассмотрю это более детально. Код прилож ения "Hello DI!" Вероятно, вы привыкли видеть примеры "Hello World", которые пишутся в одну строку кода. В этой книге мы берем нечто чрезвычайно простое и делаем его сложным. Зачем? Мы скоро доберемся до этого, но сначала давайте посмотрим, как бы выглядел пример "Hello World" с использованием механизма внедрения зависимостей. Партнеры Чтобы понять структуру программы, мы начнем с рассмотрения метода Main консольного приложения, а затем я продемонстрирую вам взаимодействующие классы: private static void Main() { IMessageWriter writer = new ConsoleMessageWriter(); var salutation = new Salutation(writer); salutation.Exclaim(); } Программа должна вводить данные в консоль, поэтому она создает новый экземпляр ConsoleMessageWriter , который как раз и инкапсулирует эту функциональность. Он передает этого автора сообщения в класс Salutation , таким образом, экземпляр Salutation знает, куда записывать эти сообщения. Поскольку на данный момент все подсоединено должным образом, вы можете выполнить логику, что приведет к тому, что сообщение будет записано на экране. Рисунок 1-11 демонстрирует взаимоотношения между партнерами. Рисунок 1-11: Метод Main создает новые экземпляр ы как класса ConsoleMessageWriter , так и класса Salutation ConsoleMessageWriter реализует интерфейс IMessageWriter , который используется Salutation . В сущности Salutation использует ConsoleMessageWriter , несмотря на то, что это непрямое использование не продемонстрировано. Основная логика приложения инкапсулирована в классе Salutation , что продемонстрировано в следующем листинге. 20 Листинг 1-1: Класс Salutation 1. public class Salutation 2. { 3. private readonly IMessageWriter writer; 4. public Salutation(IMessageWriter writer) 5. { 6. if (writer == null) 7. { 8. throw new ArgumentNullException("writer"); 9. } 10. this.writer = writer; 11. } 12. public void Exclaim() 13. { 14. this.writer.Write("Hello DI!"); 15. } 16. } Строка 4: Внедряет зависимость Строка 14: Использует зависимость Класс Salutation зависит от пользовательского интерфейса под названием IMessageWriter и запрашивает экземпляр этого интерфейса через его конструктор. Этот процесс называется внедрением через конструктор (Constructor Injection) и описывается подробно в главе 4, которая также содержит более детальный анализ похожего примера кода. Экземпляр IMessageWriter впоследствии используется в реализации метода Exclaim , который записывает соответствующее сообщение в зависимость. IMessageWriter – это простой интерфейс, определенный следующим образом: public interface IMessageWriter { void Write(string message); } Он мог бы иметь другие элементы, но в этом простом примере вам нужен только метод Write . Этот интерфейс реализуется с помощью класса ConsoleMessageWriter , который метод Main передает в класс Salutation : public class ConsoleMessageWriter : IMessageWriter { public void Write(string message) { Console.WriteLine(message); } } Класс ConsoleMessageWriter реализует IMessageWriter путем упаковывания класса Console из библиотеки базовых классов. Это простое приложение паттерна проектирования Adapter, о котором мы говорили в разделе "Осознание цели DI". 21 Возможно, вам интересно будет узнать о преимуществе замены однострочного кода на два класса и интерфейс, объем которых составляет 11 строк, и это вполне справедливо. Существует несколько преимуществ, которые можно получить, поступая таким образом. Преимущества DI Чем предыдущий пример лучше обычного однострочного кода, который мы обычно используем для реализации "Hello W orld" в C#? В этом примере механизм DI прибавляет издержок в размере 1,100%, но как только сложность кода возрастает от одной строки до десятков тысяч строк, эти издержки сокращаются и почти исчезают. Глава 2 предоставляет более сложный пример применения механизма внедрения зависимостей, и, несмотря на то, что этот пример все еще слишком прост по сравнению с реальными приложениями, вы должны заметить, что механизм DI менее навязчивый. Я не виню вас в том, что вы можете найти предыдущий пример слишком надуманным, но обдумайте следующее: по своей сущности классический пример "Hello World" – это простая проблема с хорошо заданными и ограниченными требованиями. В реальном мире разработка программного обеспечения никогда не происходит таким образом. Требования изменяются и часто являются довольно расплывчатыми. Возможности, которые вам необходимо реализовывать, также стремятся к усложнению. Механизм внедрения зависимостей помогает решать такие вопросы путем разрешения слабого связывания. В частности мы получаем преимущества, перечисленные в таблице 1-1. В таблице 1-1 я сначала указал такое преимущество как "позднее связывание", потому что, по моему опыту, это является самым важным для большинства людей. Когда архитекторы и разработчики не осознают преимуществ слабого связывания, это, главным образом, происходит, потому что они не рассматривают другие преимущества. Таблица 1-1: Преимущества, получаемые при использовании слабого связывания. Каждое преимущество доступно всегда, но может быть по-разному оценено в зависимости от обстоятельств. Преимущество Описание |