Страница 1 из 1

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

СообщениеДобавлено: 12.05.2010 10:57:54
Climber
Кажется, похожее кто-то уже спрашивал, но кто и где, не помню.
У меня есть интерфейс и объект, который его реализует:
Код: Выделить всё
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} должна решать проблему, но у меня не решает.
Можно эту проблему решить или придется делать всем объектам общего предка, реализующего интерфейс, и приводить к нему?

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

СообщениеДобавлено: 12.05.2010 11:38:11
Sergei I. Gorelkin
Либо через общего предка, либо с помощью (FList.Objects[i] as ISubscriber).Notify(AEvent);
В первом случае тебе и интерфейс не нужен. Во втором случае (с COM-интерфейсами) объект может быть уничтожен, если на него нет других ссылок.

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

СообщениеДобавлено: 12.05.2010 11:51:18
Climber
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"...

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

СообщениеДобавлено: 12.05.2010 12:55:46
Sergei I. Gorelkin
Invalid type cast означает, что таки не удается получить нужный интерфейс из имеющегося объекта.
Боюсь, без полного примера ничего более конкретного сказать не смогу...

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

СообщениеДобавлено: 12.05.2010 13:45:23
Climber
Полный пример - запросто.
Я пытаюсь сделать реализацию шаблона 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, но сути проблемы это не меняет.

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

СообщениеДобавлено: 12.05.2010 15:17:26
Sergei I. Gorelkin
Проверил. С транковой версией FPC от 13 марта сего года работает.
Видимо, будет работать и для более старых версий, если не использовать corba-интерфейсы.
Оператор 'as' для corba долгое время не работал, его пофиксили сравнительно недавно.

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

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