Обобщенные интерфейсы в Delphi

Malcolm Groves, “Generic Interfaces in Delphi”, public translation into Russian from English More about this translation.

Translate into another language.

Большинство примеров использования дженериков в Delphi используют класс с дженерик-типом. Однако, работая над своим проектом, я решил, что мне нужен интерфейс с дженерик-типом.

В проекте используется встроенный механизм издатель-подписчик. Я захотел чтобы подписчик имел для каждого типа события отдельный метод Receive, а не отдельный метод с огромным case-выражением, выбирающим действие для каждого типа события. Также я не хотел определять интерфейс для каждого типа события. Мне был нужен дженерик интерфейс подписчика, который получает тип события, как параметр.

Однако, я понятия не имел, могу ли я определить дженерик интерфейс, не говоря уже о реализации. Даже если предположить, что я могу сделать это, сможет ли Delphi выбрать правильный метод Receive для вызова? Есть только один способ узнать ...

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

Сперва я описал несколько простых событий. Их содержание не так интересно:

TSomeEvent = class

// прочее

end;

TSomeOtherEvent = class

// прочее

end;

Затем, я определил дженерик интерфейс

ISubscriber<T> = interface

procedure Receive(Event : T);

end;

Этот интерфейс должен быть реализован подписчиками для получения событий разного типа. Заметьте, тип событий записан как джененрик тип <T>.

Затем, подписчики должны реализовать интерфейс для каждого типа событий, которые они хотят принимать. Поскольку это дженерик интерфейс - это довольно просто:

TMySubscribingObject = class(TInterfacedObject, ISubscriber<TSomeEvent>, ISubscriber<TSomeOtherEvent>)

protected

procedure Receive(Event : TSomeEvent); overload;

procedure Receive(Event : TSomeOtherEvent); overload;

end;

Здесь нет описания интерфейсов ISomeEventSubscriber и ISomeOtherEventSubscriber, мы можем просто использовать ISubscriber<T> и передать тип на месте. Для этого нужно реализовать обязательно перегруженный метод Receive.

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

И это работает? С первой попытки - нет, не получилось. Независимо от того, какой тип события и через какой интерфейс я передавал, всегда выполнялся последний метод Receive.

dunit_generic_interfaces Жизненное правило №37: Если речь идет о выборе между запутывающимся Малькольм и разработчиками компилятора Delphi, вероятно, это ошибка Малькольма.

Да, моя ошибка. Барри Келли указал на ошибки моего подхода. Я описал дженерик интерфейс с GUID. Привычка. Это означает, что ISubscriber<TSomeEvent> и ISubscriber<TSomeOtherEvent>, и любые другие интерфейсы определенные с этого дженерика будут иметь одинаковые GUID. Вместе с использованием оператора "as" для получения ссылки из экземпляра TMySubscribingObject, это путало Delphi и заставляло всегда возвращать одну и туже ссылку на интерфейс.

Удалил GUID и "as" - все заработало замечательно.

В будущих сообщениях покажу другие части: издателя и Брокера событий. Интересный побочный эффект определения событий, которыми интерисуется класс, в том, что брокер событий может просто проверить интерфейсы реализованные подписчиками, чтобы узнать на какие события они подписаны.

Original (English): Generic Interfaces in Delphi

Translation: © r3code .

translated.by crowd

Like this translation? Share it or bookmark!