Главная страница   /   2.3. Расширение шаблонного приложения (Внедрение зависимостей в .NET

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

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

Марк Симан

2.3. Расширение шаблонного приложения

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

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

Архитектура

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

Рисунок 2-22: Уровень презентационной модели вставляется в шаблонное приложение для того, чтобы отделить логику представления от центральной части приложения.

Я переместил все контроллеры и модели представлений из уровня пользовательского интерфейса в уровень презентационной модели, оставляя в уровне пользовательского интерфейса только представления (файлы .aspx и .ascx) и Composition Root.

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

"Скромный" объект

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

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

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

Ядро приложения, которое содержит только достаточный минимум кода для начальной загрузки самого себя, после чего оно делегирует всю остальную часть работы тестируемым модулям, называется скромным объектом (Humble object). В данном случае он содержит только представления и код начальной загрузки: Composition Root.

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

Возможность добавления в корзину

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

Следующий логичный шаг – ввести возможность добавления покупок в корзину. Рисунок 2-23 демонстрирует скриншот использования корзины покупок.

Рисунок 2-23: Впечатляюще скудная по возможностям корзина покупок в реорганизованном коммерческом шаблонном приложении.

Чтобы обеспечить корзину покупок для каждого пользователя, мне понадобятся Basket, BasketRepository и хост поддерживаемых классов. Если вы похожи на меня, то вы захотите сначала увидеть класс Basket: рисунок 2-24 демонстрирует корзину и список ее элементов.

Рисунок 2-24: Корзина и ее содержимое, которое представляет собой список Extent<Evaluated-Product>. Extent представляет количество данного товара.

С точки зрения механизма внедрения зависимостей классы Extent и Basket не особенно интересны: они оба являются POCO-классами без зависимостей. Больший интерес представляют классы BasketService и поддерживаемые классы, продемонстрированные на рисунке 2-25.

Рисунок 2-25: BasketService и поддерживаемые классы. BasketService может извлекать и вычислять Basket для данного пользователя. BasketService использует BasketRepository для извлечения Basket и BasketDiscountPolicy с целью применения скидки (при необходимости)

BasketService может использоваться для извлечения пользовательского Basket и применения скидок. Он использует абстрактный BasketRepository, чтобы получить содержимое Basket и абстрактного BasketDiscountPolicy для дальнейшего применения скидок. Обе эти абстракции внедряются в BasketService посредством технологии внедрения через конструктор.

public BasketService(BasketRepository repository,
	BasketDiscountPolicy discountPolicy)

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

public RepositoryBasketDiscountPolicy(DiscountRepository repository)

Для управления всем этим я могу воспользоваться BasketService, чтобы распределить операции над Basket: добавление элементов, а также отображение и очистка Basket. Для выполнения этого BasketService необходимы как BasketRepository, так и BasketDiscountPolicy, которые (как вы догадались) передаются в него через его конструктор:

public BasketService(BasketRepository repository,
	BasketDiscountPolicy discountPolicy)

Для дальнейшего усложнения мне необходим ASP.NET MVC контроллер с названием BasketController, содержащий интерфейс IBasketService, который я снова внедряю через конструктор:

public BasketController(IBasketService basketService)

Как показывает рисунок 2-25, класс BasketService реализует IBasketService, это и есть используемая нами реализация.

BasketController, в конечном счете, создается при помощи пользовательского IControllerFactory, поэтому ей также потребуются эти абстракции.

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

Рисунок 2-26: Композиция шаблонного коммерческого приложения с добавленной возможностью отправки товаров в корзину, а также первоначальным списком рекомендуемых товаров на титульной странице. Каждый класс инкапсулирует свое содержание, и только Composition Root знает обо всех зависимостях.

Пользовательский IControllerFactory создает экземпляры BasketController и HomeController, предоставляя их вместе с соответствующими зависимостями. BasketService, например, использует переданный экземпляр BasketDiscountPolicy, чтобы применить стратегию скидок к корзине товаров:

var discountedBasket = this.discountPolicy.Apply(b);

Я не намекаю на то, что в данном случае переданный BasketDiscountPolicy – это экземпляр RepositoryBasketDiscountPolicy, который сам по себе является контейнером для DiscountRepository.

Это расширенное шаблонное приложение служит основой для большинства примеров кода оставшейся части книги.