В предыдущем посте мы сделали набор реализаций IPersistence для сохранения объектов:
- FilePersistence - сохраняет в файл;
- DbPersistence - в базу данных;
- WsPersistence - отправляет на web-сервис.
Очевидно, что для работы наши persistence должны обладать наборами параметров - имя файла для FilePersistence, строка соединения для DbPersistence и т.д. Параметры должны храниться в конфиг-файле.
Можно возложить обязанность получения параметров на сам persistence. Но есть способ лучше - применить инверсию и возложить получение параметров на Unity.
Получение параметров через Unity
Рассмотрим на примере DbPersistence.У DbPersistence есть один параметр - ConnectionString, передающийся в конструктор.
В Unity есть стандартное расширение InjectedMembers, которое предназначено для инъекции значение в создаваемый объект. Оно может вносить как значения, требующий дальнейшего разрешения, так и константные значения.
Пример при описании контейнера в xml-конфиге:
Не будем спорить - хорошо или нет описывать контейнер в xml-конфиге. Тут есть другая проблема - мы объединяем в одном месте конфигурацию структуры приложения (регистрация типов и зависимостей) и пользовательскую (или админскую) конфигурацию (строка соединения, для FilePersistence - имя файла). Плюс конфигурирование строки соединения выглядит сложным и многословным.
Вынесем строку соединения в отдельный конфиг. При запуске приложения в коде считаем ее и сконфигурируем контейнер Unity ею:
Но что будет, если параметры в конфиге изменятся в процессе работы приложения? Т.к. параметры считывались один раз при запуске, то для применения их необходимо перезапустить приложение.
Попытаемся добиться того, чтобы обращение к конфигу происходило при каждом разрешении интерфейса IPersistence.
Поддержка изменения параметров в Runtime
Нам нужна возможность передавать в качестве значения в InjectedMembers.ConfigureInjectionFor делегат, который при своем вызове вернет значение из конфига.Посмотрим на работу InjectedMembers.
InjectedMembers.ConfigureInjectionFor принимает в параметрах список инъекций. Инъекции могут быть двух типов: InjectionConstructor (для конструктора) и InjectionProperty (для свойств). Нас интересует конструктор.
InjectionConstructor принимает список объектов (количество должно быть равно количеству параметров конструктора реализации). Каждый объект может быть или непосредственно значением параметра (как в примере выше) или экземпляром наследника InjectionParameterValue (таким образом например реализуются параметры, которые требуют разрешения).
Стандартного способа для создания инъекции с делегатом нет. Поэтому напишем свою реализацию.
Сначала опишем делегат:
Теперь собственного наследника от InjectionParameterValue. Сделаем его generic-типом. TValue - тип параметра для которого мы будем получать значение.
Получение значения параметра происходит через механизм политик. Про него можно прочитать в предыдущем посте. Мы должны создать свою политику IDependencyResolverPolicy и в ее методе Resolve вызвать наш делегат.
В DelegatedInjectionValue переопределим абстрактный метод GetResolverPolicy и вернем нашу политику:
Результат
В результате мы используем это так:При каждом разрешении типа IPersistence будет создаваться объект DbPersistence, вызываться наш делегат и его результат передаваться в конструктор DbPersistence.