Motto

В тихом саду здравомыслия
Пусть на вас постоянно падают
кокосовые орехи пробужденности.
Чогьям Трунгпа РИНПОЧЕ


Версия для мобильного


пятница, 29 марта 2013 г.

Головокружительные возможности DI и Delphi Spring. Часть 9. Один интерфейс – несколько реализаций.

Это последний перевод из серии про внедрение зависимостей на примере использования Delphi Spring.

Это перевод публикации Ника Ходжеса от 07 ноября 2011 года: Getting Giddy with Dependency Injection and Delphi Spring #9 – One Interface, Many Implementations. (перевод сделан с разрешения автора).

Предыдущие публикации


Как обычно, 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.


Ссылки по теме

5 комментариев:

  1. ... каша?!
    если я правильно понимаю каша возможно и будет ... но лишь в контейнере - в коде будет чисто
    В качестве примера - система имеющая множество фреймов регистрирующихся внутри себя в секции initialization
    Внутри главного заполнение списка модулей
    Ну а затем вызов фрейма по его имени

    ОтветитьУдалить
    Ответы
    1. Так в том-то и дело, что не в контейнере каша будет, а в тех местах - где имена будут использоваться (в местах регистраций и получений экземпляров). Я против использования строковых констант в качестве идентификаторов. Для этого есть перечисления. Исключение - когда имя объекта возвращается из стороннего сервиса или базы данных, - в этом случае строка лучше волшебного непонятного идентификатора (integer или GUID-а или-лили ешё какого).

      Удалить
  2. Вот это получение интерфейса по имени, закодированного явно, ничуть не лучше явного использования нужного класса.
    Однако если это строковое значение тем или иным образом выбирается пользователем - то почему бы и нет? Остаётся всего лишь дать пользователю выбор.
    По-моему, это вполне разумный компромисс, хотя лично мне такой вариант (пока) тоже не очень нравится... надо этим научиться пользоваться.

    ОтветитьУдалить
  3. Если у вас нет желания переводить части с 1й по 5ю, то может быть вы напишите некое их обобщение своими словами.

    ОтветитьУдалить
    Ответы
    1. 4 статьи Ник посвятил мотицации для использования DI. Причём сделал это слишком многословно, имхо.

      Читайте в Википедии и ищите в интернете информацию по:
      * о Solid - пяти основных принципах дизайна классов в объектно-ориентированном проектировании — Single responsibility, Open-closed, Liskov substitution, Interface segregation и Dependency inversion
      * 2 статьи Ник посвятил Закону Деметры

      Удалить

Постоянные читатели