Главная страница
Навигация по странице:

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

  • Почему пользовательский интерфейс зависит от библиотеки доступа к данным

  • Внедрение зависимостей или И нверсия управления

  • Внедрение зависимостей в. Внедрение зависимостей в .NET. Руководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей


    Скачать 5.66 Mb.
    НазваниеРуководство по применению этого механизма. Net приложениях. Книга демонстрирует основные паттерны на обычном языке C#, поэтому вы в полной мере поймете, как работает механизм внедрения зависимостей
    АнкорВнедрение зависимостей в .net
    Дата14.12.2019
    Размер5.66 Mb.
    Формат файлаpdf
    Имя файлаВнедрение зависимостей в .NET.pdf
    ТипРуководство
    #100226
    страница3 из 43
    1   2   3   4   5   6   7   8   9   ...   43

    Featured Products


    2.

    3.
    <% var products = (IEnumerable
    )this.ViewData["Products"];
    4.
    foreach (var product in products)
    5.
    { %>
    6.

    7.
    <%= this.Html.Encode(product.Name) %>
    8.
    (<%= this.Html.Encode(product.UnitPrice.ToString("C")) %>)
    9.

    10.
    <% } %>
    11.

    Строка 3: Получает товары, заполненные при помощи контроллера
    ASP.NET MVC позволяет вам писать стандартный HTML-код с фрагментами императивного кода, который вставляется для получения доступа к объектам, созданным и переданным контроллером, который создал представление. В данном случае метод
    Index класса
    HomeController передал список рекомендуемых товаров ключу с названием
    Products
    , который Мэри использует в представлении для того, чтобы отображать список товаров.
    Рисунок 2-8 демонстрирует, как Мэри реализовала архитектуры, показанную на рисунке
    2-2.
    Рисунок 2-8: На данный момент Мэри реализовала все три уровня приложения. Этот рисунок идентичен рисунку 2-2, но повторяется здесь для того, чтобы проиллюстрировать текущее состояние приложения Мэри.

    50
    Когда все три уровня находятся на своих местах, теоретически приложения должны работать, но только тест может проверить, действительно ли они работают.
    Дымовой тест (Smoke test)
    На данный момент Мэри реализовала все три уровня, поэтому пришло время понять, работает ли приложение. Она нажимает клавишу
    F5
    и вскоре получает сообщение:
    Указанное именованное соединение либо не найдено в конфигурации, либо не
    предназначено для использования в рамках EntityClient провайдера, либо не валидно.
    Поскольку Мэри использовала конструктор по умолчанию
    CommerceObjectContext
    (продемонстрированный в листинге 2-1), то по смыслу ожидается, что строка соединения под названием
    CommerceObjectContext присутствует в файле web.config
    . Как я и упоминал в обсуждении листинга 2-2, эта имплицитность содержит в себе ловушку. За ночь Мэри забыла детали реализации ее доменного уровня. Код компилируется, но сайт не работает.
    В этом случае фиксирование ошибки является прямолинейным. Мэри вставляет корректную строку соединения в файл web.config
    . При запуске приложения появляется продемонстрированная на рисунке 2-3 страница.
    Возможность "Р екомендуемые товары" на данный момент реализована, и М эри чувствует в себе уверенность и готовность реализовывать следующу ю возможность приложения. В конце концов, она последовала доказанным оптимальным методам и создала трехуровневое приложение.
    Анализ
    Удалось ли Мэри создать совершенное, многоуровневое приложение? Нет, не удалось – несмотря на то, что у нее наверняка были самые наилучшие намерения. Она создала три
    Visual Studio проекта, которые соответствуют трем уровням запланированно й архитектуры, что продемонстрировано на рисунке 2-9. Для обычного наблюдателя такая реализация похожа на желаемую архитектуру, но как вы увидите, такой код является сильно связанным.
    Рисунок 2-9: В вэб-приложении электронной коммерции, созданном Мэри, присутствует по одному Visual Studio проекту для каждого уровня запланированно й архитектуры – но является ли оно трехуровневым?
    Visual Studio делает такой способ работы с решениями и проектами более простым и естественным. Если нам нужна функциональность из разных библиотек, то мы можем с легкостью добавить ссылку на нее и написать код, который создаст новые экземпляры

    51 типов, определенных в этих библиотеках. Каждый раз при добавлении ссылки мы используем зависимость.
    Диаграмма зависимосте й
    При работе с решениями в Visual Studio очень легко потерять путь перемещения важных зависимостей, поскольку Visual Studio показывает их вместе со всеми остальными ссылками проекта, которые могут указывать на сборки стандартной библиотеки классов
    .NET (BCL).
    Для того чтобы понять, как модули приложения Мэри соотносятся друг с другом, мы можем нарисовать диаграмму зависимостей (см. рисунок 2-10).
    Рисунок 2-10: Диаграмма зависимостей приложения Мэри, показывающая, каким образом модули зависят друг от друга. Стрелки указывают на зависимость модуля
    Самым примечательным знанием, полученным из рисунка 2-10, является то, что библиотека пользовательского интерфейса (User Int erface library) зависит как от доменной библиотеки (Dom ain library), так и от библиотеки доступа к данным (Data Access library).
    Кажется, что пользовательский интерфейс может обходиться в некоторых случаях без доменного уровня. Это предположение несет за собой дальнейшие исследования.
    Анализ композиции
    Главная цель создания трехуровневого приложения – разделить сущности. Нам бы хотелось отделить нашу доменную модель от уровня доступа к данным и уровня пользовательского интерфейса таким образом, чтобы ни одна из этих сущностей не засоряла доменную модель. Для больших приложений существенной является возможность работы с одной областью изолированно.
    Для того чтобы оценить реализацию Мэри, мы можем задать простой вопрос:
    Тест

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

    52 существующие модули вместе новыми и неожиданными способами, но в идеале, мы должны иметь возможность делать это без необходимости модифицировать существующие модули.
    Примечание
    Следующий анализ рассматривает вопрос того, можно ли заменять модули, но знайте, что это методика, которую мы используем для анализа композиции. Даже если нам никогда не нужно будет заменять модули, этот вид анализа раскрывает потенциальные вопросы, касающиеся связывания. Если мы обнаружим, что код сильно связан, то все преимущества слабого связывания будут утеряны.
    Можем ли мы использовать модули приложения Мэри новыми и увлекательными способами? Давайте рассмотрим некоторые возможные сценарии.
    Новый пользовательский интерфейс
    Если приложение Мэри будет иметь успех, то заинтересованные стороны проекта захотят, чтобы она разработала полную клиентску ю версию в системе построения клиентских приложений Windows – Windows Presentation Foundation (W PF). Возможно ли это сделать во время повторного использования доменного уровня и уровня доступа к данным?
    При рассмотрении диаграммы зависимостей на рисунке 2-10 мы можем быстро определить, что ни один из модулей не зависит от пользовательс кого веб-интерфейса, поэтому можно удалить его и заменить на WPF пользовательский интерфейс.
    Создание полной клиентской версии на базе WPF – это новое приложение, которое использует большинство своих реализаций совместно с первоначальным веб- приложением. Рисунок 2-11 иллюстрирует, как WPF приложению может понадобиться использовать те же самые зависимости, что и веб-приложение. Первоначальное веб- приложение может оставаться неизмененным.
    Рисунок 2-11: Замена пользовательского веб-интерфейса пользовательским WPF интерфейсом возможна, так как ни один из модулей не зависит от пользовательского веб- интерфейса. Первоначальный пользовательский веб-интерфейс на рисунке остается выделенным серым цветом для того, чтобы проиллюстрировать, что добавление нового пользовательского интерфейса не исключает первоначальный пользовательский интерфейс
    Замена уровня пользовательского интерфейса наверняка возможна в реализации Мэри, поэтому давайте рассмотрим еще одну интересную декомпозицию.

    53
    Н овы й уровень доступа к данным
    Представьте себе, что рыночные аналитики поймут, что для оптимизации прибыли приложение Мэри должно быть доступным в виде облачного приложения, размещенного на W indows Azure. В W indows Azure данные могут храниться в весьма масштабируемом сервисе Azure T able Storage Service. Этот механизм хранения данных основан на гибких data-контейнерах, которые содержат не свободные данные. Сервис не навязывает никакой конкретной схемы базы данных, и отсутствует соответствующая целостность.
    Протокол, используемый для взаимодействия с Table Storage Service, – это HT TP, а наиболее очевидная .NET технология доступа к данным основывается на ADO.NET Data
    Services.
    Т акой тип базы данных иногда называют базы данных ключ-значение, и это несколько другой зверь, нежели реляционная база данных, доступ к которой осуществляется посредством Ent ity Framework.
    Чтобы дать возможность использовать приложение электронной коммерции в виде облачного приложения, библиотеку доступа к данным можно заменить модулем, который использует T able Storage Service. Возможно ли это?
    Из диаграммы зависимостей рисунка 2-10 мы уже знаем, что и библиотека пользовательского интерфейса, и доменная библиотека зависят от библиотеки доступа к данным, которая базируется на Entity Framework. Если мы попытаемся удалить библиотеку доступа к данным, то решение больше не будет компилироваться, поскольку отсутствует необходимая зависимость.
    В большом приложении, состоящем из дюжины модулей, мы также могли бы попытаться удалить те модули, которые не компилируют ся, чтобы увидеть, что останется. Что касается приложения Мэри, то здесь очевидно, что нам пришлось бы удалить все модули, ничего при этом не оставляя.
    Несмотря на то, что можно было бы разработать библиотеку Azure T able Data Access, которая имитирует API, используемое первоначальной библиотекой доступа к данным, нет ни одного способа, с помощью которого мы бы могли внедрить ее в приложение.
    Приложение еще не настолько компонуемо, насколько того хотели бы заинтересованные стороны. Для разрешения облачных возможностей, которые максимально увеличивают прибыль, необходимо значительно переписать приложение, поскольку ни один из существующих модулей нельзя использовать повторно.
    Рисунок 2-12: Попытка удаления реляционной библиотеки доступа к данным приводит к тому, что ничего не остается, потому что все остальные модули зависят от нее. Нет ни одного места, где мы могли дать доменной библиотеке указание использовать новую библиотеку Azure T able Data Access вместо первоначально используемой

    54
    Другие комбинации
    Мы могли бы проанализироват ь приложение на возможность других комбинаций модуля, но это было бы спорным вопросом, поскольку мы уже знаем, что они не поддерживают важный сценарий.
    Кроме того, не все комбинации имеют смысл. Мы могли бы поинтересоваться, можно ли заменить доменную модель другой реализацией. В большинстве случаев это было бы странным вопросом, потому что доменная модель инкапсулирует сердце приложения. Без доменной модели большинство приложений не имеют оснований для существования.
    Анализ
    Почему реализация Мэри не достигла желаемой степени компоновки? Не произошло ли это, потому что пользовательский интерфейс напрямую зависит от библиотеки доступа к данным? Давайте исследуем эту возможность более подробно.
    Анализ диаграммы зависимостей

    Почему пользовательский интерфейс зависит от библиотеки доступа к данным?
    Виновником этого является сигнатура метода данной доменной модели: public IEnumerable<
    Product> GetFeaturedProducts(bool isCustomerPreferred)
    Product: Раскрывает тип доступа к данным для клиентов
    Метод
    GetFeaturedProducts возвращает последовательность товаров, но класс
    Product определен в библиотеке доступа к данным. Любой клиент, использующий метод
    GetFeaturedProducts должен ссылаться на библиотеку доступа к данным для возможности компиляции.
    Можно изменить сигнатуру метода так, чтобы возвращалась последовательность типов, определенных в доменной модели. Такой подход был бы еще и более корректным, но это не решает имеющуюся проблему.
    Давайте предположим, что мы разрушаем зависимость между библиотекой пользовательского интерфейса и библиотекой доступа к данным. Измененная диаграмма зависимостей выглядела бы сейчас так, как показано на рисунке 2-13.

    55
    Рисунок 2-13: Диаграмма зависимостей гипотетической ситуации, при которой разрывается зависимость пользовательского интерфейса от библиотеки доступа к данным
    Позволило бы Мэри такое изменение заменить реляционную библиотеку доступа к данным такой библиотекой, которая инкапсулирует доступ к сервису Azure T able? К несчастью, не позволило бы, поскольку доменная библиотека все еще зависит от библиотеки доступа к данным. Пользовательский интерфейс, в свою очередь, все еще зависит от доменной модели, поэтому если мы попытаемся удалить первоначальную библиотеку доступа к данным, то в приложении ничего не останется.
    Основная причина этой проблемы находится где-то в другом месте.
    Анализ интерфейса доступа к данны м
    Доменная модель зависит от библиотеки доступа к данным, поскольку вся модель данных определена в этой библиотеке. Класс
    Product был сгенерирован, когда Мэри запускала мастер LINQ to Entities. Использование Entity Framework для реализации уровня доступа к данным может стать разумным решением.
    Как бы то ни было, не используйте его напрямую в доменной модели.
    Код, являющийся виновником возникшей проблемы, разбросан по классу
    ProductService
    . Конструктор создает новый экземпляр класса
    CommerceObjectContext и передает его в приватную переменную члена класса: this.objectContext = new CommerceObjectContext();
    Такой подход сильно связывает класс
    ProductService с библиотекой доступа к данным.
    Не существует разумного способа, с помощью которого мы могли бы перехватить этот фрагмент кода и заменить его чем-то еще. Ссылка на библиотеку доступа к данным жестко закодирована в классе
    ProductService
    Реализация метода
    GetFeaturedProducts использует
    CommerceObjectContext для того, чтобы вытянуть объекты
    Product из базы данных. var products = (from p in this.objectContext.Products where p.IsFeatured select p).AsEnumerable();

    56
    Это только усиливает жестко закодированну ю зависимость, но на данном этапе повреждения уже нанесены. Все что нам нужно – это более подходящий способ компоновки модулей без использования такого сильного связывания.
    П рочие вопросы
    Перед тем как показать вам лучшую альтернативу, мне хотелось бы обратить ваше внимание на некоторые другие вопросы, связанные с кодом Мэри, к которым обязательно нужно обратиться.

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

    Оказавшись под влиянием Дженса, Мэри решила реализовать код, который определяет, является ли пользователь привилегированным покупателем в пользовательском интерфейсе. Т ем не менее, то, каким образом покупатель идентифицируется в качестве привилегированного, является фрагментом бизнес- логики, поэтому это должно быть реализовано в доменной модели.
    Аргумент Дженса в пользу концепции разделения и принципа единственно й ответственности никак не оправдывает размещение кода в неправильном месте.
    Следование принципу единственной ответственности в рамках единичной библиотеки вполне возможно – это ожидаемый подход.

    Класс
    ProductService полагается на XML конфигурацию. Как вы видели при наблюдении за стараниями Мэри, она забыла, что ей пришлось поместить часть конфигурационного кода в ее файл web.config
    . Несмотря на то, что способность конфигурирования компилируемого приложения является важной, только окончательное приложение должно полагаться на конфигурационные файлы.
    Удобнее, когда повторно используемые библиотеки обязательно конфигурируются вызывающими их объектами.
    В конце концов, конечный вызывающий объект сам по себе является приложением.
    На данном этапе все соответствующие конфигурационные данные можно прочитать из
    .config файла и загрузить при необходимости в основные библиотеки.

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

    57 2.2. Как действовать правильно
    Механизм внедрения зависимостей (DI) можно было использовать для решения тех вопросов, которые мы обсуждали. Поскольку DI – это радикальное отклонение от того способа, с помощью которого Мэри создавала свое приложение, я не собираюсь его изменять. Вместо этого я собираюсь заново создать его с самого начала.
    Вам не следует делать из этого решения вывод о том, что можно выполнить рефакторинг существующего приложения относительно DI; это можно сделать, но это очень сложно.
    По моему опыту для этого потребуется много раз выполнять рефакторинг.
    П римечание
    Пока я буду вести вас этому примеру, не волнуйтесь, если вы вдруг потеряетесь по пути.
    Механизм внедрения зависимостей очень сложный, и в нем множество элементов. Я выбрал этот пример, потому что он похож на реалистичный сценарий, но недостаток этого примера – это то, что он сложнее, чем выдуманный пример. В дальнейшем в этой книге я собираюсь погрузиться глубже в те сущности и методики, которые были введены здесь.
    После того, как вы прочитаете больше информации, вы всегда сможете вернуться обратно и заново прочитать этот раздел.
    Многие люди называют механизм внедрения зависимостей инверсией управления
    (Inversion of Control). Иногда эти термины используются как взаимозаменяемые, но DI – это подмножест во инверсии управления. На протяжении всей этой книги я буду последовательно использовать самый конкретный термин: DI. Если я буду иметь ввиду инверсию управления, то я буду говорить об этом конкретно.

    Внедрение зависимостей или И нверсия управления?
    Т ермин инверсия управления (IoC) первоначально означал любой вид стиля программирования, в котором полноценный фреймворк или исполняющая среда контролировали ход выполнения программы. Согласно этому определению большинство программного обеспечения, разработанного на .NET Fram ework, использует принцип инверсии управления.
    При написании ASP.NET приложения вы попадаете в жизненный цикл ASP.NET страницы, но вы ничем не управляете, всем управляет ASP.NET.
    При написании WCF сервиса вы реализуете интерфейсы, помеченные атрибутами. Вы можете писать код сервиса, но, в конце концов, вы ничем не управляете, всем управляет
    W CF.
    В настоящее время мы настолько привыкли работать с фреймворками, что уже не считаем их особенными, но эта модель уже не находится под полным вашим контролем. Это все еще может случиться и с .NET приложением – наиболее заметно при выполнении команд командной строки. Как только вызывается
    Main
    , ваш код приобретает полный контроль.
    Он контролирует ход выполнения программы, жизненный цикл, в общем все. Никаких особенных событий не происходит, и никакие переопределенные члены не вызываются.
    До того момента, как механизм внедрения зависимостей приобрел свое название, люди начинали называть фреймворки, которые управляли зависимостями, IoC-контейнерам и, и

    58 вскоре смысл IoC постепенно сместился к конкретному значению: инверсия управления зависимостями. Будучи постоянным систематиком, Мартин Фаулер ввел термин
    внедрение зависимост ей для того, чтобы в частности ссылаться на IoC в контексте управления зависимостями. С тех пор внедрение зависимост ей широко применяется в качестве самого корректного термина.
    Короче говоря, инверсия управления – более широкой термин, который содержит в себе термин внедрение зависимостей, но не ограничивается им.
    В контексте управления зависимостями инверсия управления в точности описывает то, что мы пытаемся выполнить. В приложении Мэри код напрямую управляет его зависимостями: когда
    ProductService требуется новый экземпляр класса
    CommerceObjectContext
    , он просто создает экземпляр с помощью ключевого слова new
    Когда
    HomeController требуется новый экземпляр класса
    ProductService
    , он также создает новый экземпляр с помощью ключевого слова new
    . Приложение находится под тотальным контролем. Это может звучать сильно, но в действительност и контроль ограничен. Я называю это анти-паттерном Cont rol Freak (прим. ред. – термин в психологии характеризует человека, который пытается диктовать всем вокруг как, по его мнению, должно все происходить. В переводе, с точки зрения анти-паттерна, упоминается как
    Руководитель-нарко ман, но мы оставим английский вариант). Инверсия управления дает нам указание отпустить этот контроль и позволить еще кому-то управлять зависимостями.
    Создание коммерческого приложения заново
    При написании программного обеспечения я предпочитаю приступать к этому в наиболее значимом месте. Чаще всего это пользовательский интерфейс. Поэтому я работаю, добавляя еще больше функциональности до тех пор, пока возможность не осуществлена, и я могу перейти к следующей возможности. Эта методика "снаружи-внутрь" помогает мне сфокусироваться на запрашиваемо й функционально сти без необходимости заново перестраивать приложение.
    П римечание
    Методика "снаружи-внутрь" тесно связана с принципом YAGNI ("You Aren’t Gonna Need
    It ") – "Вам это не понадобится". Этот принцип подчеркивает, что необходимо реализовывать только требуемые возможности, и что реализация должна быть настолько простой, насколько это возможно.
    Т ак как я всегда практикую разработку через тестирование (T DD), я начинаю писать модульные тесты, как только используемый мной подход "снаружи-внутрь" начинает намекать мне на создание нового класса. Несмотря на то, что я написал множество модульных тестов, чтобы создать этот пример, разработка через тестирование не требует реализации и использования механизма внедрения зависимостей, поэтому я не собираюсь демонстрировать эти тесты в моей книге. Если вам интересно, то эти тесты доступны в исходном коде, который приложен к этой книге.
    П ользовательский интерфейс
    Необходимым условием для списка рекомендуемых товаров является написание приложения, которое извлекает рекомендуемые товары из базы данных и отображает их в списке, как это показано на рисунке 2-3. Поскольку я знаю, что заинтересованным

    59 сторонам проекта, главным образом, будет интересен визуальный результат, то лучше всего начать с пользовательского интерфейса.
    Первое, что я делаю после открытия Visual Studio – добавляю в мое решение новое
    ASP.NET MVC приложение. Т ак как список рекомендуемых товаров должен продолжать титульную страницу, я начинаю с изменения
    Index.aspx таким образом, чтобы она содержала разметку, продемонстрированную в следующем листинге.
    Листинг 2-4: Разметка
    Index представления
    1   2   3   4   5   6   7   8   9   ...   43


    написать администратору сайта