【问题标题】:Are Delphi interfaces inherited in subclassesDelphi 接口是否在子类中继承
【发布时间】:2009-02-05 10:23:50
【问题描述】:

如果我在基类上实现一个接口,它会被它的子类继承吗,我知道函数/过程会,但我更感兴趣的是我是否能够将子类转换为接口,然后回到原来的类。

我希望我能做的是将不同基类的对象传递给一个函数,然后在函数中确定类型并酌情使用它们。

这可能吗?这是正确的方法吗?

更新

为了帮助消除任何混乱(或创造更多),这是我想做的(精简到其核心)。

界面

    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

我已经更新了代码,所以更好地展示我所追求的。

//这里的代码部分与传递给它的对象几乎没有关系,例如,如果这个类是 TAccounts 并且它被传递了一个 TEmployee 对象,它可能会在那里支付每周工资,但是如果是 TInvoice,那么它会检查是否需要付款,并且仅在截止日期前 2 天时才付款。

TEmployee/TInvoice 甚至可能来自要求付款的外部班级。

这只是一个例子。

【问题讨论】:

  • 我更正了你的类定义,因为它是递归的。如果不是您的意思,请随意撤消它,但我很确定我是对的。
  • 请同时修复接口,IMyInterface 和 ITransferable 都有。
  • 感谢提醒,已修复

标签: delphi oop delphi-2007


【解决方案1】:

是的,接口是子类继承的。

从子类转换到接口是完全可以接受的。

但是,如果我读错了您的问题,但如果“然后回到原来的班级”意味着 . . .

你有接口 I,A 类和 B 类。 A 实现 I,B 继承 A,你可能可以,但真的不应该从 A 转换为 B。

编辑:

你想从 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”语句是一个坏主意,并且违反了 OO 原则。如果你需要这样做,那么要么

  • 接口定义为 不足,您可能需要添加一个 将属性键入接口 允许您(如果 iObj.SomeProperty = 1) 那么 . . .)
  • 界面根本不是 这个问题的正确解决方案,以及 您应该将参考传递为 TSubMyObject。

编辑 3:

@mghie:我同意你的观点,我没有很好解释的是 SomeProperty 有一些数据允许函数在那里分支,消除了类型检查的依赖性。 SomeProperty 不应该“简单地”替换类型检查(例如,通过将类名放入属性中,然后检查类名)这确实是完全相同的问题。

继承接口的子类之间有一些本质的区别。这种差异应由以下任一方式表示

  • 暴露一些数据项,可以 然后在分支中使用

例如

if(item.Color = Red) then 
   item.ContrastColor := Blue;
else
   item.ContrastColor := Red;
  • 或通过多态性,例如

IEmployee定义了CalculatePay方法,TManager和TWorker实现了IEmployee,各自在CalculatePay方法中有不同的逻辑。

如果打算做第一种情况,多态性可能是矫枉过正(多态性并不能解决所有问题)。

编辑 4

你说“这里的 //code 部分与传递给它的对象几乎没有关系......” 很抱歉,如果你需要支付,那句话是不正确的员工,您需要知道他们的 1) EmployeeCode 2) 他们的工资详细信息 3) 他们的银行详细信息等,如果您要收取发票,您需要 1) InvoiceNumber 2) Invoice Amount 3) CustomerCode 要收取的费用等。 . . 这是多态性的理想场所

假设使用接口的函数检查“帐户”是否需要对对象执行某些操作(例如,向员工付款、收取发票等)。所以我们可以调用函数 AccountsCheck。在 Accounts 检查中,您将有一个特定于每个子类的逻辑(支付员工工资、收取发票......)这是多态性的理想候选者。

在您的接口上(或在另一个接口上,或作为子类上的虚拟方法)定义一个“AccountsCheck”方法。然后每个派生类都有自己的 Accounts 检查实现。

代码从庞大的单个 AccountsCheck 函数中移出,并移到每个子类上的较小函数中。这使得代码

  • 意图更明显(每个类 包含一些逻辑 帐户检查)
  • 您不太可能破坏子类 B在修复某些东西时的逻辑 C 的 AccountsCheck
  • 更容易准确判断 SubClass B 的 AccountsCheck 逻辑是什么 是,你只需要检查 20 行 小 AccountsCheck 中的代码,而不是 200 在General AccountsCheck)

这还有更多“充分的理由”,如果有人想编辑/发布 cmets,请这样做。

如果您发现需要在 AccountsCheck 的实现之间共享一些逻辑,请创建一些实用程序函数,不要在每个函数中重新实现相同的轮子。

多态是解决问题的方法。

【讨论】:

  • 那么如何访问子类中的方法呢?我想要的是从 B 到 I 到 B。
  • 您不能将 A 转换为 B,但可以将 B 转换为 I,这就是问题所在。
  • @Binary Worrier:恕我直言,Type 属性既不比检查类类型本身好也不差。多态性应该消除两者的需要。
  • @Binary Worrier:我完全同意您在 EDIT 3 中的观点。很难用 OP 给出的简化示例来说明对象继承是否必要。但接口应该以任何一种方式工作。
  • @edit 4,你的权利确实与传递的对象有关,我只是试图传达它是属于帐户的代码,它可能只是将对象传递给一个部分在处理付款的帐户中,并且该对象在那里使用。
【解决方案2】:

我的建议是不要对类进行强制转换,而是对另一个接口进行强制转换。将您的 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 的“自定义”代码也将应用于任何 ITS 后代,而无需任何进一步的自定义代码。 IOtherObjectIntf 接口仅用作“是的,我是其中之一”的指示器,它允许您的代码正确分支。当然,它浪费了另一个Guid......但是他们有这么多,谁会注意到? :)

【讨论】:

    【解决方案3】:

    接口由子类继承,您可以将对象转换为接口,但将接口转换为类是不安全的(或不推荐)。如果您需要这样做,您可能使用了错误的接口方式。

    【讨论】:

      【解决方案4】:

      对于如何理解您的问题似乎有些疑问,实际上在您对this answer 的评论中,您说您想“从 B 到 I 到 B”。

      这确实不推荐,只能通过使用有关如何在类上实现接口的信息来支持。

      如果我对您的理解正确,那么您要做的是将接口传递给某个方法,并在该方法中根据实现接口的具体类执行不同的操作。但是,一旦您开始使用接口,最好继续使用它们。你可以让接口有一个方法来返回实现类,但你不应该对接口在哪个类中实现做任何假设——这会让你失去对接口编程的一些好处。

      您可以做的是创建不同的接口,并仅在(某些)您的后代类中实现其中的一些。然后您可以在传递的接口指针上使用 QueryInterface()Supports()。对于您的基类,这将返回 nil,但对于实现该接口的所有后代类,它将返回一个指针,让您只调用它们拥有的方法。

      编辑:例如在 OmniThreadLibrary 中,您会发现:

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

      您可以将其添加到您的界面中。但同样,恕我直言,使用不同的接口要好得多。

      【讨论】:

        【解决方案5】:

        您不能将接口直接转换为对象(这不是接口的用途),但有时这样做非常实用,以至于您无法抗拒......

        如果你真的想做,可以直接在IMyInterFace中使用mghie给出的示例“IOmniWorker”:

        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”传递给函数,您可以避免不必要的引用计数。

        【讨论】:

          猜你喜欢
          • 2020-12-27
          • 1970-01-01
          • 1970-01-01
          • 2011-08-28
          • 1970-01-01
          • 1970-01-01
          • 2021-12-21
          相关资源
          最近更新 更多