Требуется ли для делегирования унаследованного интерфейса класс-оболочка?

Delphi позволяет делегировать интерфейс с помощью ключевого слова implements.

Например

IIndep1 = interface
  function foo2: integer;
end;

IIndep2 = interface
  function goo2: integer;
end;

TIndep1And2 = class(TInterfacedObject, IIndep1, IIndep2)
private
  FNested : IIndep1; //e.g. passed via constructor or internally created (not shown here)
public
   Constructor Create(AIndep1: IIndep1);
  function goo2: integer;
   property AsIndep1 : IIndep1 read FNested implements IIndep1;
end;

Это хорошо работает, но не для унаследованных интерфейсов. (Сообщение об ошибке «Отсутствует реализация метода интерфейса ILev1.foo»)

ILev1 = interface
  function foo: Integer;
end;

ILev2 = interface(ILev1)
  function goo: Integer;
end;

TLev2Fails = class(TInterfacedObject, ILev1, ILev2) //Also fails with ILev2 alone (Error: "ILev1 not mentioned in interface list")
private
  FNested : ILev1; //passed via constructor or internally created 
public
   Constructor Create(AILev1: ILev1);
   function goo: Integer;
   property AsLev1 : ILev1 read FNested implements ILev1;
end;

Обходной путь - добавить дополнительный класс предка

TLev1Wrapper = class(TInterfacedObject, ILev1)
private
  FNested : ILev1; //passed via constructor or internally created 
public
   Constructor Create(AILev1: ILev1);
   property AsLev1 : ILev1 read FNested implements ILev1;
end;

TLev2Works = class(TLev1Wrapper, ILev2)
public
  function goo: Integer;
end;

Есть ли способ избежать предка класса-оболочки?

[EDIT] Просто примечание о делегировании интерфейса, цель использования implements состоит в том, чтобы избежать прямого удовлетворения интерфейса, но передать это требование агрегированному или составному члену. Предоставление полного интерфейса и ручное делегирование составному члену сводит на нет преимущества, полученные от использования implements для управления интерфейсом. На самом деле, в этом случае ключевое слово и свойство implements могут быть удалены.


person Jasper Schellingerhout    schedule 18.08.2015    source источник
comment
Я думаю, что это должно быть ошибкой компилятора. Я просмотрел документацию и не вижу ничего, относящегося к этому ограничению.   -  person Graymatter    schedule 19.08.2015
comment
Престижность @RudyVeldthuis за указание на то, что проблема связана с ILev2.foo, который нельзя связать.   -  person Jasper Schellingerhout    schedule 19.08.2015
comment
@Graymatter спасибо за ссылки в ваших комментариях ниже. Я хожу взад и вперед о том, является ли это ошибкой. Вполне может быть один   -  person Jasper Schellingerhout    schedule 19.08.2015


Ответы (2)


Похоже, что компилятор пытается применить ожидания (читай: требования) IUnknown.QueryInterface:

Для любого объекта конкретный запрос интерфейса IUnknown на любом из интерфейсов объекта должен всегда возвращать одно и то же значение указателя.

Если бы вы могли делегировать реализацию базового интерфейса при самостоятельной реализации производного интерфейса, то:

obj := TLev2Fails.Create(otherLev1);  // Assuming your class could compile

lev1 := obj as ILev1;     // yields reference to otherLev1 implementor
lev2 := obj as ILev2;     // yields reference to TLev2Fails instance

unk1 := lev1 as IUnknown;   // returns IUnknown of otherLev1 implementor
unk2 := lev2 as IUnknown;   // returns IUnknown of obj TLev2fails instance

Этого бы не произошло, если бы ваш вложенный объект был правильно реализован как производный класс TAggregatedObject, но компилятор не имеет возможности узнать, так ли это, не говоря уже о его принудительном применении, поэтому вместо этого кажется, что он просто требует что, если вы реализуете производный интерфейс, вы также должны напрямую реализовать любые интерфейсы, которые наследует сам интерфейс.

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

Если вы хотите делегировать в этом случае, вы должны "делегировать вручную".

Хотя при этом теряется преимущество средства implements, оно, по крайней мере, сохраняет преимущества повторного использования, просто не так удобно.

ПРИМЕЧАНИЕ. Даже если ваша делегированная реализация основана на TAggregatedObject, компилятор все равно не может определить, что эти детали реализации удовлетворяют требованиям QueryInterface, поэтому вы все равно получите эту ошибку (даже если используете ссылку на класс для делегированного интерфейса).

Вы по-прежнему должны делегировать вручную.

Сказав все это, я в настоящее время не вижу, чем это отличается для случая, когда интерфейсы задействованы без отношений наследования, но вполне возможно, что это действительно так, и я просто не проработал все необходимые «мысленные эксперименты». чтобы доказать это себе. :)

Возможно, компилятор просто проявляет особую осторожность в ситуациях, когда он думает, что может/должен быть, и в документации просто не упоминается об этом получающемся в результате ограничении implements.

Или это может быть ошибка в компиляторе, хотя я думаю, что есть достаточно очевидные причины для такого поведения, и поведение само по себе настолько хорошо установлено и последовательно (воспроизводя точно такое же поведение в Delphi 7), что упущение в соответствующей документации является более вероятное объяснение.

person Deltics    schedule 19.08.2015
comment
Можете ли вы опубликовать решение, используя TAggregatedObject, пожалуйста? Ожидается, что запросы к делегированному интерфейсу и непосредственно реализованному интерфейсу будут заканчиваться на разных объектах. Ваш пример, измененный для TIndep1And2, даст аналогичные результаты с IUnknown для двух разных объектов. Унаследованный случай проблема хуже. В вашем примере могло быть lev2 as ILev1, тогда у вас было бы ILev1 для двух объектов. Тем не менее, если в одном случае это не соблюдается, то почему в другом? - person Jasper Schellingerhout; 19.08.2015
comment
Использование TAggregatedObject по-прежнему не облегчает использование implements: вам все равно придется делегировать полномочия вручную. TAggegratedObject просто заботится об отражении IUnknown объекта-контейнера. Что касается того, почему в одном случае, а не в другом: компилятор не может быть так же уверен, что независимые интерфейсы не должны быть реализованы независимо, как может быть, что унаследованные интерфейсы не должны. Это объясняет потенциальное объяснение различий в поведении, но не меняет того факта, что компилятор может ошибаться в любом случае. - person Deltics; 19.08.2015
comment
Я согласен с той частью, где вы сказали, что не вижу, чем это отличается от случая, когда интерфейсы задействованы без отношений наследования. Что-то я делаю довольно часто. Я удалил свой комментарий, но добавлю его снова. Это должно быть ошибкой компилятора. - person Graymatter; 19.08.2015
comment
Как насчет ситуации, когда у вас есть один класс с 2 implements? Не столкнется ли это с той же проблемой. Если вы посмотрите на этот мой вопрос и ответ Дэвида (stackoverflow.com/ вопросов/22649433/), я думаю, что объект, как его видит Delphi, меняется в зависимости от того, к какому интерфейсу осуществляется доступ и как к нему обращаются (а также в зависимости от того, какая версия Delphi используется). Таким образом, хотя мы видим один и тот же объект в нашем коде, объект для интерфейса — это объект, на который подсчитываются ссылки, и именно он используется для IUnknown. - person Graymatter; 19.08.2015
comment
@graymatter - любые задействованные объекты полностью зависят от реализации, компилятор не может на это повлиять. Никакая магия компилятора не может изменить то, что дает ваша реализация QueryInterface. Я не знаю, почему вы думаете, что это зависит от используемой версии Delphi. Насколько я могу судить, поведение одинаково от Delphi 7 до Delphi XE8. Ясно, что это ошибка компилятора, но это не обязательно означает, что это ошибка. Просто ошибка в соответствии с правилами компилятора, точно так же, как присваивание целого числа строке приводит к ошибке компилятора. :) - person Deltics; 19.08.2015
comment
Посмотрите на вопрос, который я связал. Базовый объект отличается в зависимости от используемой версии Delphi. Например, в XE2 и более ранних версиях базовым объектом для Supports при использовании implements, как это property CC: TObjectC read FObjectC implements IInterfaceY, является объект, реализующий интерфейс. В XE3 и более поздних версиях это объект, который включает объявление реализации. Это отличается от использования метода для возврата средства реализации. - person Graymatter; 19.08.2015
comment
@graymatter - ваш вопрос связан с другими сложностями, которые объясняют различия, наблюдаемые в версиях Delphi. В частности, явное участие IInterface и делегирование типа класса. И да, использование двух предложений реализации действительно удовлетворяет компилятор, что приводит к возможности того, что компилятор принял неправильную реализацию. Но это ничем не отличается от любого количества других подобных устройств, чтобы заглушить жалобы компилятора, когда вы потенциально делаете что-то неправильно, от чего компилятор пытается вас спасти и т. д. - person Deltics; 19.08.2015
comment
Давайте продолжим это обсуждение в чате. - person Graymatter; 19.08.2015

Есть ли способ избежать предка класса-оболочки?

Нет, унаследованный интерфейс должен быть привязан к классу-предку. Delphi не связывает унаследованные интерфейсы неявно по причинам, объясненным здесь. Кроме того, он не может привязываться к текущему классу, если только он вручную не делегирует все вызовы составному элементу.

У нас остается только один вариант. Класс-оболочка-предок, к которому мы можем привязать ILev1

person Jasper Schellingerhout    schedule 16.09.2016