Приведение объекта к интерфейсу

Форум для изучающих FPC и их учителей.

Модератор: Модераторы

Приведение объекта к интерфейсу

Сообщение Climber » 12.05.2010 10:57:54

Кажется, похожее кто-то уже спрашивал, но кто и где, не помню.
У меня есть интерфейс и объект, который его реализует:
Код: Выделить всё
type
  ISubscriber = interface
    procedure Notify(AEvent: string);
  end;

  { TSubscriber }

  TSubscriber = class (TSomeClass, ISubscriber)
  public
    procedure Notify(AEvent: string); virtual;
  end;

А еще у меня есть список TStringList, в который добавляются ссылки на эти объекты (причем объекты могут быть разными и не иметь общего предка, но они все наследники от TObject и теоретически реализуют интерфейс ISubscriber. Я даже морально готов получать AV, если интерфейс не реализован в объекте в списке). И я хочу вызывать этот интерфейс из списка:
Код: Выделить всё
       TSubscriber (FList.Objects[i]).Notify(AEvent); // 1. это работает
       ISubscriber(FList.Objects[i]).Notify(AEvent);  // 2. это не работает - "Class or Object types "TObject" and "ISubscriber" are not related"
Способ 2 не компилируется.
В этом топике написано, что директива {$OBJECTCHECKS OFF} должна решать проблему, но у меня не решает.
Можно эту проблему решить или придется делать всем объектам общего предка, реализующего интерфейс, и приводить к нему?
Climber
постоялец
 
Сообщения: 415
Зарегистрирован: 03.06.2007 20:09:57
Откуда: Москва

Re: Приведение объекта к интерфейсу

Сообщение Sergei I. Gorelkin » 12.05.2010 11:38:11

Либо через общего предка, либо с помощью (FList.Objects[i] as ISubscriber).Notify(AEvent);
В первом случае тебе и интерфейс не нужен. Во втором случае (с COM-интерфейсами) объект может быть уничтожен, если на него нет других ссылок.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1406
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Приведение объекта к интерфейсу

Сообщение Climber » 12.05.2010 11:51:18

Sergei I. Gorelkin писал(а): либо с помощью (FList.Objects[i] as ISubscriber).Notify(AEvent);

Хм... Добавил GUID и скомпилировалось...
Я всегда думал, что ISubscriber(FList.Objects[i]) и (FList.Objects[i] as ISubscriber) - это эквивалентные конструкции :oops:
Поэтому даже и попробовать не догадался...

Добавлено спустя 5 минут 56 секунд:
Извиняюсь, поспешил...
Компилируется, но при выполнении возникает ошибка "Invalid Type Cast"...
Climber
постоялец
 
Сообщения: 415
Зарегистрирован: 03.06.2007 20:09:57
Откуда: Москва

Re: Приведение объекта к интерфейсу

Сообщение Sergei I. Gorelkin » 12.05.2010 12:55:46

Invalid type cast означает, что таки не удается получить нужный интерфейс из имеющегося объекта.
Боюсь, без полного примера ничего более конкретного сказать не смогу...
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1406
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Приведение объекта к интерфейсу

Сообщение Climber » 12.05.2010 13:45:23

Полный пример - запросто.
Я пытаюсь сделать реализацию шаблона Publisher - Subscriber. Готовые реализации в количестве 1 штуки для Delphi видел краем глаза, и она мне не понравилась.
Подписчик:
Код: Выделить всё
{$OBJECTCHECKS ON}
{$INTERFACES CORBA}
interface
uses  Classes, SysUtils, Forms;
type
  { ISubscriber }
  ISubscriber = interface
  ['{7B3C558B-00BE-424D-91AC-5F9D894CC51B}']
    procedure Notify(AEvent: string);
  end;

  { TSubscriber }
  TSubscriber = class (TForm, ISubscriber)   // TForm взят с потолка, можно подставить любой другой класс по вкусу...
  public
    procedure Notify(AEvent: string); virtual;
  end;

implementation

{ TSubscriber }
procedure TSubscriber.Notify(AEvent: string);
begin

end;

Publisher:
Код: Выделить всё
uses
  Classes, SysUtils, Subscriber;

type

  { TPublisher }

  TPublisher = class
  private
    FList: TStringList;
  public
    constructor Create;
    destructor Destroy; override;
    function SubscriptionIndex(ASubscriber: TSubscriber; AEvent: string): longint;
    procedure Subscribe(ASubscriber: TSubscriber; AEvent: string); virtual;
    procedure Unsubscribe(ASubscriber: TSubscriber; AEvent: string); virtual;
    procedure Notify(AEvent: string); virtual;
  end;

implementation

procedure TPublisher.Notify(AEvent: string);
{ Уведомляет всех подписчиков на событие AEvent о том, что оно случилось. }
var i: longint;
begin
  for i:=0 to FList.Count-1 do
    if AEvent=FList.Strings[i] then
       (FList.Objects[i] as ISubscriber).Notify(AEvent);  //  Ошибка тут
end;
Общий принцип работы Publisher'a: подписчики подписываются на определенные события (название события хранятся как строки). В Publisher'e есть список TStringList, который хранит набор строк и связанных с ними объектов. Таким образом каждый объект может подписаться на несколько разных событий и получить информацию о том, какое именно случилось. Остальные методы не привожу, чтобы не загромождать текст.
Вот код тестирующей процедуры:
Код: Выделить всё
procedure TPublisherTestCase.NotifyTest;
var event: string;
    s1, s2, s3: TTestSubscriber;
begin
  s1:=TTestSubscriber.Create(nil);
  s2:=TTestSubscriber.Create(nil);
  s3:=TTestSubscriber.Create(nil);
  p.Subscribe(s1, 'event1');
  p.Subscribe(s2, 'event2');
  p.Subscribe(s3, 'event3');
  p.Subscribe(s2, 'event4');
  p.Notify('event2');
  AssertEquals('Subscriber was not notified', 'event2', s2.NotifyRecieved);
  FreeAndNil(s1);
  FreeAndNil(s2);
  FreeAndNil(s3);
end;
Во вложении - проект для fpcunit, там полные тексты обоих модулей и тесткейс для тестирования класса.

Добавлено спустя 5 минут 29 секунд:
P. S. TTestSubscriber - потомок TSubscriber, но сути проблемы это не меняет.
Вложения
publisher.zip
(93.33 КБ) Скачиваний: 479
Climber
постоялец
 
Сообщения: 415
Зарегистрирован: 03.06.2007 20:09:57
Откуда: Москва

Re: Приведение объекта к интерфейсу

Сообщение Sergei I. Gorelkin » 12.05.2010 15:17:26

Проверил. С транковой версией FPC от 13 марта сего года работает.
Видимо, будет работать и для более старых версий, если не использовать corba-интерфейсы.
Оператор 'as' для corba долгое время не работал, его пофиксили сравнительно недавно.
Аватара пользователя
Sergei I. Gorelkin
энтузиаст
 
Сообщения: 1406
Зарегистрирован: 24.07.2005 14:40:41
Откуда: Зеленоград

Re: Приведение объекта к интерфейсу

Сообщение Climber » 13.05.2010 11:12:13

О, кажется, заработало!
Тогда самый последний вопрос (для общего образования).
Сначала я сделал интерфейс и реализовал его в объекте.
Компилятор потребовал дописать реализации методов _AddRef и _DelRef (там еще третий был, не помню). Я полез разбираться и нашел, что это лечится директивой {$INTERFACES CORBA}. Вставил ее, компилятор перестал требовать эти методы.
Потом я попытался применить конструкцию FList.Objects[i] as ISubscriber и узнал от компилятора, что мне нужен GUID. Сгенерировал его (заодно узнал, что это такое 8) ), потом убрал директиву {$INTERFACES CORBA}. Компилятор не стал требовать методы _AddRef и _DelRef. Самое интересное, что теперь я убираю GUID, а _AddRef и _DelRef все равно не нужны.
Что там на самом деле происходило?
И еще: {$OBJECTCHECKS ON} что дает? Видел в примерах, но пока не понял сути явления...
Climber
постоялец
 
Сообщения: 415
Зарегистрирован: 03.06.2007 20:09:57
Откуда: Москва


Вернуться в Обучение Free Pascal

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 4

Рейтинг@Mail.ru