Это перевод публикации Ника Ходжеса от 07 ноября 2011 года: Getting Giddy with Dependency Injection and Delphi Spring #9 – One Interface, Many Implementations. (перевод сделан с разрешения автора).
Все переводы по Spring
- Я не переводил части с 1й по 5ю, потому что мне было лень - там очень много текста, и, по моему скромному мнению, эти части не содержат конкретной информации об использовании Delphi Spring, а постепенно подводят к необходимости использования Dependency Injection. Причём, Ник ведёт к этому слишком длинным и запутанным путём. См. Ссылки по теме.
- DI и Delphi Spring. Часть 5. Основы Delphi Spring.
- DI и Delphi Spring. Часть 6. Обойдёмся без конструктора.
- DI и Delphi Spring. Часть 7. Контроль над созданием.
- DI и Delphi Spring. Часть 8. Разное.
- DI и Delphi Spring. Часть 9. Один интерфейс несколько реализаций.
Как обычно, Delphi Spring Framework можно скачать с GoogleCode
До сих пор мы регистрировали интерфейсы и их реализации в соотношении один к одному. Для каждого интерфейса регистрировался только один реализующий класс. Но что, если мы хотим реализовать интерфейс разными способами, выбирая реализацию в зависимости от выбора пользователя или других внешних факторов?
К счастью для нас, контейнер Spring предоставляет такую возможность. Система регистрации контейнера в фреймворке Delphi Spring позволяет при каждой регистрации указать имя предоставляемой реализации, давая таким образом возможность отличать одну регистрацию от другой, даже если они регистрируют разные реализации для одного и того же интерфейса.
При регистрации нескольких реализаций одного интерфейса без указания имени «победит последний».
Давайте объявим простой интерфейс для кредитной карты следующим образом:
type ICreditCard = interface ['{6490640C-0E2B-4F7D-908C-0E6A74DCC0A0}'] function IsValid(aCreditCardNumber: string): boolean; function ChargeAmount(aCreditCardNumber: string; aAmount: Double): Boolean; end;Существует множество кредитных карт, которые могут использовать клиенты, поэтому нам понадобятся разные реализации карт для различных вендоров:
GlobalContainer.RegisterType<TVisa>.Implements<ICreditCard>('VISA'); GlobalContainer.RegisterType<TMasterCard>.Implements<ICreditCard>('MasterCard'); GlobalContainer.RegisterType<TDiscover>.Implements<ICreditCard>('Discover'); GlobalContainer.RegisterType<TAMEX>.Implements<ICreditCard>('AMEX');Этот код регистрирует четыре разных класса (TVisa, TMasterCard, TDiscover, TAMEX) для одного и того же интерфейса (ICreditCard) с помощью строкового параметра при вызове GetService. Как только они будут зарегистрированы, вы сможете выбрать какой класс вы использовать в качестве реализации ICreditCard при обработки кредитной карты. Вы даже сможете изменить свой выбор в режиме выполнения, основываясь, скажем на выборе пользователя или на различиях обработки заказов и т.п.
Например, если у вас есть четыре переключателя (radio button) которые позволяют пользователю выбрать одну из четырех кредитных карт, то вы сможете сделать следующее:
var CurrentCard: ICreditCard ... procedure TMultipleImplementationsForm.RadioButton1Click(Sender: TObject); begin CurrentCard := ServiceLocator.GetService<ICreditCard>('VISA'); end; procedure TMultipleImplementationsForm.RadioButton2Click(Sender: TObject); begin CurrentCard := ServiceLocator.GetService<ICreditCard>('MasterCard'); end; procedure TMultipleImplementationsForm.RadioButton3Click(Sender: TObject); begin CurrentCard := ServiceLocator.GetService<ICreditCard>('Discover'); end; procedure TMultipleImplementationsForm.RadioButton4Click(Sender: TObject); begin CurrentCard := ServiceLocator.GetService<ICreditCard>('AMEX'); end;Приведенный выше код присвоит экземпляр соответствующего объекта реализации в одну переменную CurrentCard в зависимости от выбранного пользователем переключателя. Соответствующий объект будет возвращён на основании строки переданной в GetService. Эта строка, конечно же, должна соответствовать строке, с которой была зарегистрирована нужная реализация объекта.
Таким образом вы можете зарегистрировать по имени и затем использовать столько реализаций интерфейса, сколько вам будет угодно. Очевидно, что это предоставляет массу возможностей, так как позволяет вам выбирать из любого количества реализаций и при этом в любой момент добавлять новые реализации. (примечание переводчика: представляю, какая там каша будет после лет 5 активного использования именованных динамической реализаций).
Пример приложения, показывающего эту технику, а также некоторые другие интересные особенности могут быть найдены в примерах, которые поставляются вместе с фреймворком Delphi Spring.
Ссылки по теме
- Cоветую почитать и подумать о SOLID.
- Habrahabr: Dependency Injection: анти-паттерны
- Habrahabr: Использование IoC контейнеров. За и Против. Обсуждение паттерна Service Locator. (.net, Unity)
- Habrahabr: DI и IoC для начинающих, часть 3. Применение Unity (.net) при написании unit-тестов. По ссылке можно найти и другие части статьи.
... каша?!
ОтветитьУдалитьесли я правильно понимаю каша возможно и будет ... но лишь в контейнере - в коде будет чисто
В качестве примера - система имеющая множество фреймов регистрирующихся внутри себя в секции initialization
Внутри главного заполнение списка модулей
Ну а затем вызов фрейма по его имени
Так в том-то и дело, что не в контейнере каша будет, а в тех местах - где имена будут использоваться (в местах регистраций и получений экземпляров). Я против использования строковых констант в качестве идентификаторов. Для этого есть перечисления. Исключение - когда имя объекта возвращается из стороннего сервиса или базы данных, - в этом случае строка лучше волшебного непонятного идентификатора (integer или GUID-а или-лили ешё какого).
УдалитьВот это получение интерфейса по имени, закодированного явно, ничуть не лучше явного использования нужного класса.
ОтветитьУдалитьОднако если это строковое значение тем или иным образом выбирается пользователем - то почему бы и нет? Остаётся всего лишь дать пользователю выбор.
По-моему, это вполне разумный компромисс, хотя лично мне такой вариант (пока) тоже не очень нравится... надо этим научиться пользоваться.
Если у вас нет желания переводить части с 1й по 5ю, то может быть вы напишите некое их обобщение своими словами.
ОтветитьУдалить4 статьи Ник посвятил мотицации для использования DI. Причём сделал это слишком многословно, имхо.
УдалитьЧитайте в Википедии и ищите в интернете информацию по:
* о Solid - пяти основных принципах дизайна классов в объектно-ориентированном проектировании — Single responsibility, Open-closed, Liskov substitution, Interface segregation и Dependency inversion
* 2 статьи Ник посвятил Закону Деметры