Экспериментируем с делегатами

Толпа делегатов

Реализации паттерна хранилище, который подвергся критике в одной из прошлых статей, практически никогда не обладают внутренним состоянием. Все, что нам обычно от них нужно — методы. Чтобы реализовать их мы вынуждены прибегать к самым жёстким связям в объектно-ориентированном дизайне. И переопределить какой-то отдельный метод, да ещё во время исполнения, становится проблемой.

С другой стороны, в .NET есть такой инструмент как делегаты, которые отождествляются исключительно с методами, без посредников. У делегатов есть тип, который, как и сигнатуры методов, определяет типы входящих параметров и возвращаемого результат. А в этом есть что-то созвучное с идеей сервера объектов.

Представим себе, что тип делегата — это протокол обмена с сервером объектов. В обобщенном виде, получая на вход HTTP-запрос и возвращая HTTP-ответ он мог бы выглядеть так:

public delegate HttpModelResponse HttpProtocol(HttpModelRequest request);

Попробуем от обобщенных моделей перейти к более приземленным и рассмотрим делегат для получения статьи данного блога. А точнее любого объекта по его псевдониму. В привычном варианте с паттерном хранилище у нас могли бы быть десятки и сотни однотипных методов в самых разных интерфейсах. Например, таких:

public interface IBlogRepository { ... BlogPostModel GetByAlias(string alias); ... }

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

public delegate TModel GetModelByAlias<TModel>(string alias);

Входная модель нашего объектного сервера — это псевдоним объекта. Выходная — параметризованая обобщенным типом TModel, которым может быть, как BlogPostModel, так и любой другой. И будь у нас соответствующий контроллер, то в нем мог бы оказаться такой метод:

public ActionResult GetPost(string alias) { GetModelByAlias<BlogPostModel> handler = ...здесь мы должны получить обработчик...; BlogPostModel model = handler(alias); return this.View(model); }

IoC-контейнер

В этом коде упущена тактика получения нужного обработчика. Попробуем компенсировать её, воспользовавшись IoC-контейнером. Например, Autofac. Безусловно внедрение зависимости в конструктор является наиболее предпочтительным способом, но в данном случае нет смысла засорять его аргументами, область действия которых ограничивается практически одним методом. Поэтому мы будем внедряться непосредственно в методы контроллера. И тогда инициализация IoC-контейнера будет расширена таким кодом:

// Добавляем возможность внедрения в качестве аргументов action-методов builder.RegisterType<ExtensibleActionInvoker() .As<IActionInvoker>() ..WithParameter("injectActionMethodParameters", true) ; // Регистрируем конкретный обработчик для выборки статьи блога builder.Register<GetById<BlogPostModel>>(context => ...здесь нам понадобится конкретная реализация...);

А action-метод контроллера мы заменим таким:

public ActionResult GetPost(string alias, GetModelByAlias<BlogPostModel> handler) { return View(handler(alias)); }

Заглушка хранилища

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

public class SampleStorage { public static readonly SampleStorage Current = new SampleStorage(); private Dictionary<string, object> _Objects = new Dictionary<string, object>() { { "test-blog-alias", new BlogPostModel() { Title = "Сервер объектов", Text = "В прошлой статье мы нашли немало недостатков в паттерне хранилище..." } } }; public TModel GetModelByAlias<TModel>(string alias) { return (TModel)_Objects[alias]; } }

И заменим регистрацию обработчика на обращение к этой заглушке.

builder.Register<GetById<BlogPostModel>>(context => SampleStorage.Current.GetModelByAlias<BlogPostModel>);

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