4

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

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

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

更新

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

界面

    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 甚至可能来自要求付款的外部班级。

这只是一个例子。

4

5 回答 5

9

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

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

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

你有接口 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 原则。如果你需要这样做,那么要么

  • 接口定义不足,您可能希望向接口添加一个 Type 属性,允许您 (如果 iObj.SomeProperty = 1) then 。. .)
  • 该接口根本不是解决此问题的正确方法,您应该将引用作为 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)发票金额 3)客户代码收取等。. . 这是多态性的理想场所

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

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

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

  • 意图更明显(每个类都包含一些用于 AccountsCheck 的逻辑)
  • 在 AccountsCheck for C 中修复某些内容时,您不太可能破坏 SubClass B 的逻辑
  • 更容易弄清楚 SubClass B 的 AccountsCheck 逻辑是什么,您只需检查小型 AccountsCheck 中的 20 行代码,而不是 General AccountsCheck 中的 200 行)

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

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

多态性是您问题的解决方案。

于 2009-02-05T10:32:22.360 回答
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......但是他们有这么多,谁会注意到?:)

于 2009-02-05T21:33:05.943 回答
1

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

于 2009-02-05T12:19:34.343 回答
1

似乎对如何理解您的问题存在一些疑问,实际上,在您对这个答案的评论中,您说您想“从 B 到 I 到 B”。

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

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

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

编辑:例如在 OmniThreadLibrary 你会发现:

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

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

于 2009-02-05T12:56:19.527 回答
1

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

如果你真的想这样做,你可以直接在 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”传递给函数,您可以避免不必要的引用计数。

于 2009-02-06T13:39:19.127 回答