Являются ли интерфейсы Delphi унаследованными в подклассах - PullRequest
4 голосов
/ 05 февраля 2009

Если я реализую интерфейс в базовом классе, будет ли он наследоваться его подклассами, я знаю, что функции / процедуры будут, но меня больше интересует, смогу ли я привести подкласс к интерфейсу, а затем вернуться к исходному классу.

Я надеюсь, что смогу передать объектам различных базовых классов функцию, а затем в функции, определенной там, ввести и использовать их соответствующим образом.

Возможно ли это и правильный ли это подход?

Обновление

, чтобы помочь устранить любую путаницу (или создать еще кое-что), вот что я хотел бы сделать (зачеркнуто до сути).

Интерфейс

    IMyInterFace = interface
   ['{B7203E50-F5CB-4755-9DB1-FB41B7B192C5}'] 
     function MyFunction: Boolean;
   end;

Базовый класс

   type TMyObject = class(TInterfacedObject,IMyInterFace)

Подкласс

  type TSubMyObject = class(TMyObject)

Другой класс

  type TMyOtherObject = class(TInterfacedObject,IMyInterFace)

Тогда использование

 procedure foo();
   var obj:TSubMyObject;
 begin
      obj := TSubMyObject.Create();
      SendInterfaceObject(obj);
 end;

 procedure bar();
   var obj:TMyOtherObject;
 begin
      obj := TMyOtherObject.Create();
      SendInterfaceObject(obj);
 end;



 procedure SendInterfaceObject(iobj:IMyInterFace);
 begin
     if (iobj is TSubMyObject) then
     begin
        //code here
     end
     else if (iobj is TMyOtherObject) then
     begin
       //code here
     end;
 end;

Обновление 2

Я обновил код abit, так что покажите, что мне нужно.

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

TEmployee / TInvoice может даже прийти из сторонних классов с просьбой произвести платеж.

это всего лишь пример.

Ответы [ 5 ]

9 голосов
/ 05 февраля 2009

Да, интерфейс наследуется подклассом.

Допустимо приведение из подкласса к интерфейсу.

Тем не менее, и извинения, если я неправильно читаю ваш вопрос, но если "а затем вернуться к исходному классу", значит. , .

У вас есть интерфейс I, класс A и класс B. A реализует I, а B наследует A, возможно, вы можете, но ДЕЙСТВИТЕЛЬНО НЕ СЛЕДУЕТ приводить от A к B.

EDIT:

Вы хотите перейти от B к I и обратно к B. , , но у вас уже есть ссылка на B, если B - это то, что вы передаете своей функции, так что вам не нужно приводить от I к B (если речь не идет о другом объекте, тогда Нет, не делайте этого)

Переход от I к B аналогичен переходу от A к B, вы пытаетесь создать цепочку наследования, что на самом деле не следует делать. Необходимость сделать это - запах кода, он говорит вам, что вы должны попытаться решить эту проблему другим способом (возможно, изменив классы (например, добавив больше свойств / методов в I), или просто решив, что функция будет работать только с подклассом - работа с подклассом 'B' даст вам доступ ко всем методам A & I).

Можете ли вы отредактировать свой вопрос и добавить пример кода того, что вы пытаетесь сделать?

РЕДАКТИРОВАТЬ 2

 procedure SendInterfaceObject(iobj:IMyInterFace);
 begin
     if (iobj is TSubMyObject) then
     begin
        //code here
     end;
 end;

Утверждение 'If' содержит плохую идею и нарушает принципы ОО. Если вам нужно сделать это, то либо

  • Определение интерфейса недостаточно, вы можете добавить Тип свойства для интерфейса позволяет вам (если iObj.SomeProperty = 1) тогда. , .)
  • Интерфейс просто не исправить решение этой проблемы и Вы должны передать ссылку как TSubMyObject.

РЕДАКТИРОВАТЬ 3:

@ mghie: Я согласен с вами, но я не очень хорошо объяснил, что SomeProperty имеет некоторые данные, которые позволяют функции переходить туда, устраняя зависимость проверки типа. SomeProperty не должен «просто» заменять проверку типа (например, помещая имя класса в свойство, а затем проверяя имя класса). Это действительно точно такая же проблема.

Существует некоторое существенное отличие между подклассами, которые наследуют интерфейс. Эта разница должна быть выражена либо

  • Предоставление некоторого элемента данных, который может затем использовать в Brach

например.

if(item.Color = Red) then 
   item.ContrastColor := Blue;
else
   item.ContrastColor := Red;
  • Или через полиморфизм, например.

IEmployee определяет метод CalculatePay, TManager и TWorker реализуют IEmployee, каждый со своей логикой в ​​методах CalculatePay.

Если целью было сделать что-то похожее на первый случай, полиморфизм может быть излишним (полиморфизм не решает все проблемы).

РЕДАКТИРОВАТЬ 4

Вы говорите "разделы // кода здесь имеют мало общего с объектом, который ему передается ..." Извините, но это утверждение неверно, если вам нужно заплатить Сотрудник, вам необходимо знать его 1) Код сотрудника 2) Данные о зарплате 3) Их банковские реквизиты и т. Д., Если вы выставляете счет на оплату необходимого вам счета 1) Счет-фактура 2) Сумма счета-фактуры 3) Код клиента, который нужно списать с счета и т. , , это идеальное место для полиморфизма .

Допустим, функция выполняет проверку интерфейса, чтобы определить, нужно ли «Учетным записям» что-то делать с объектом (например, оплатить сотруднику, выставить счет и т. Д.). Таким образом, мы могли бы вызвать функцию AccountsCheck. При проверке внутренних счетов у вас будет частичка логики, характерная для каждого подкласса (для оплаты сотрудника, для выставления счета ...). Это идеальный кандидат на полиморфизм.

На вашем интерфейсе (или на другом интерфейсе, или как виртуальный метод в подклассе) Определите метод "AccountsCheck". Затем каждый производный класс получает собственную реализацию проверки учетных записей.

Код перемещается из вашей огромной единственной функции AccountsCheck и в меньшие функции в каждом подклассе. Это делает код

  • Более очевидно в намерении (каждый класс содержит некоторую логику для AccountsCheck)
  • Вы реже сломаете подкласс Логика Б при исправлении чего-либо в Проверка учетных записей для C
  • проще точно разобраться какая логика для AccountsCheck подкласса B есть, вы только чтобы проверить 20 строк код в маленькой учетной записи, не 200 в общих учетных записях проверки)

Для этого есть и другие "веские причины", если кто-то хочет редактировать / оставлять комментарии, пожалуйста, сделайте это.

Если вам нужно разделить логику между реализациями AccountsCheck, создайте несколько служебных функций, не переопределяйте одно и то же колесо в каждой.

Полиморфизм - решение вашей проблемы.

2 голосов
/ 06 февраля 2009

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

type
  IOtherObjectIntf = interface
    ['{FD86EE29-ABCA-4D50-B32A-24A7E71486A7}']
  end;

type 
  TMyOtherObject = class(TInterfacedObject,IMyInterFace,IOtherObjectIntf)

, а затем измените вашу другую процедуру на:

procedure SendInterfaceObject(iobj:IMyInterFace);
begin
  if Supports(iObj,IOtherObjectIntf) then
    begin
      //code here for TMyOtherObject
    end
  else 
    begin
      //code here for other standard implementations
    end;
end;

Таким образом, ваш «пользовательский» код для TMyOtherObject будет также применяться к любому из его потомков без какого-либо дополнительного пользовательского кода. Интерфейс IOtherObjectIntf используется как не что иное, как индикатор «да, я один из тех», который позволяет вашему коду правильно переходить. Конечно, он опустошает другого Гуида ... но их так много, кто бы заметил? :)

1 голос
/ 06 февраля 2009

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

Если вы действительно хотите сделать это, вы можете использовать пример «IOmniWorker», приведенный mghie непосредственно в IMyInterFace:

IMyInterFace = interface
['{B7203E50-F5CB-4755-9DB1-FB41B7B192C5}'] 
  function MyFunction: Boolean;
  function GetImplementor: TObject;
end;

Реализации функций выглядят так:

function TMyObject.GetImplementor: TObject;
begin
  Result := Self;
end;
function TMyOtherObject.GetImplementor: TObject;
begin
  Result := Self;
end; 

SendInterfaceObject выглядит тогда так:

procedure SendInterfaceObject(const iobj:IMyInterFace);
begin
  if (iobj.GetImplementor is TSubMyObject) then
  begin
     //code here
  end
  else if (iobj.GetImplementor is TMyOtherObject) then
  begin
    //code here
  end;
end;

Кстати, я добавил (очень) небольшую оптимизацию: передав iobj как "const" в функцию, вы можете избежать ненужного подсчета ссылок.

1 голос
/ 05 февраля 2009

Кажется, есть некоторые сомнения в том, как следует понимать ваш вопрос, и действительно, в своем комментарии к этому ответу вы говорите, что хотите "перейти от B к I к B".

Это действительно не рекомендуется и поддерживается только при использовании информации о том, как интерфейсы реализованы в классе.

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

Вместо этого вы можете создавать различные интерфейсы и внедрять некоторые из них только в (некоторые из) ваших классов-потомков. Затем вы можете использовать QueryInterface () или Supports () в переданном указателе интерфейса. Для вашего базового класса это вернет nil , но для всех классов-потомков, которые реализуют интерфейс, он вернет указатель, который позволяет вам вызывать методы только у них.

Редактировать: Например, в библиотеке OmniThread вы найдете:

IOmniWorker = interface
  ['{CA63E8C2-9B0E-4BFA-A527-31B2FCD8F413}']
  function  GetImplementor: TObject;
  ...
end;

которые вы можете добавить в свой интерфейс. Но опять же, ИМХО, использование различных интерфейсов намного лучше.

1 голос
/ 05 февраля 2009

Интерфейс наследуется подклассами, и вы можете приводить объекты к интерфейсу, но не безопасно (или не рекомендуется) приводить интерфейс к классу. Если вам нужно сделать это, вы, вероятно, используете интерфейсы неправильно.

...