Delphi接口委托实现不能直接调用

时间:2017-04-04 15:24:54

标签: delphi delphi-5

我知道这是旧的,我正在使用Delphi 5.不确定它是否在Delphi的后续版本中是相同的。

我发现如果一个对象将其接口实现委托给另一个对象,那么原始对象就可以直接调用interfaced方法,我希望委托接口的实现应该是什么。

这是我的问题

IFoo = interface
  procedure InterfaceMethod;
end;

TBar = class(TComponent, IFoo)
  FFoo: IFoo;
  procedure ObjectMethod;
  property Foo: IFoo read FFoo implements IFoo;
end;

TBar.Method2
begin
  InterfaceMethod;  // this will give compile error, Method1 not declared!
  (Self as IFoo).InterfaceMethod;  // compile error, saying operation not supported.
  FFoo.InterfaceMethod;  // this work, but meaningless, since this can be any object, why the need of interface!
end;

如果我在TBar课程之外这样做的话。说

Bar := TBar.Create;
Bar.InterfaceMethod;  // compile error
(Bar as IFoo).InterfaceMethod;  // compile error
Bar.Foo.Method1;  // this work, but...

任何人都可以解释为什么会这样或者我的理解不正确。或者在D5之后这可能是正确的吗?

以下是示例代码

unit iwSqlDbEngine;
//
// Database resource and helper funcions.
//
interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Db, ScktComp, ExtCtrls;

type

  ISqlMaker = interface
    ['{5A671E7B-957B-4A52-BB5E-87EEA8E5687B}']
    procedure SetSqlStatement(const value: string);
    function GetSqlStatement: string;
    // Insert and update
    function Insert(table: string): ISqlMaker;
    function Update(table: string): ISqlMaker;
    function AddField(fieldname: string; value: variant; isExpr: Boolean = False): ISqlMaker; overload;
    function AddField(field: TField): ISqlMaker; overload;
    // Delete
    function Delete(table: string): ISqlMaker;
    // Select
    function Select(table: string; columns: array of string): ISqlMaker;
    function Join(table: string; columns, matches: array of string): ISqlMaker;
    function LeftJoin(table: string; columns, matches: array of string): ISqlMaker;
    // Other clauses
    function Where(aExpr: string): ISqlMaker;
    function GroupBy(columns: array of string): ISqlMaker;
    function OrderBy(columns: array of string): ISqlMaker;
    function Having(vExprs: array of string): ISqlMaker;
    property SqlStatement: string read GetSqlStatement write SetSqlStatement;
  end;

  TSampleDbEngine = class(TComponent, ISqlMaker)
  private
    FSqlMaker: ISqlMaker;
  public
    procedure Execute;
    property SqlMaker: ISqlMaker read FSqlMaker write FSqlMaker implements ISqlMaker;
  end;

implementation

procedure TSampleDbEngine.Execute;
begin
  (Self as ISqlMaker).GetSqlStatement;
end;

end.

与上述相同的编译错误。

2 个答案:

答案 0 :(得分:3)

您收到错误&#34; InterfaceMethod未声明&#34;因为您的<? $sample = ' ;'; $arr = file('text.txt'); $handle = fopen('text.txt','w+'); foreach($arr as $string){ if(strpos($string,$sample)===false) fwrite($handle,$string); } fclose($handle); 变量属于Bar类型,而不是TBar,并且由于IFoo没有TBar,因此编译器是正确的。在InterfaceMethod内,TBar.Method2的类型为Self,这就是为什么它也无法在那里工作。

转换为TBar的想法是正确的,但它失败了,因为IFoo运算符要求接口具有GUID,而您as没有。在IFoo之后立即放置插入符号,然后按 Ctrl + G 生成接口的GUID。

我无法访问Delphi 5,但我刚刚使用Delphi 7进行了测试,它应该与D5非常相似,代码如下

IFoo = interface

现在&#34;课外&#34;两个

  IFoo = interface
  ['{B6AFF17B-C239-414D-84DE-F8F2AE1FE4E0}']
    procedure Bar;
  end;

  TFoo = class(TComponent, IFoo)
  private
    FFoo: IFoo;
  public
    property Foo: IFoo read FFoo implements IFoo;
  end;

var f: TFoo;
begin
  f := TFoo.Create(nil);
  (f as IFoo).Bar;
end;

编译得很好。

答案 1 :(得分:2)

您收到错误InterfaceMethod not declared,因为在 TBar 方法的上下文中,默认范围(self)是 TBar ,而不是<强>的IFoo

如果 TBar 直接实施 IFoo ,则 TBar 类(通常)会同时拥有您尝试调用的方法。即,您是否有 TBar 参考或 IFoo 参考,两者都支持该方法。如果你在 TBar 上有一个接口method resolution clause来实现一个特定的接口方法,并在实现类中使用另一个名称的方法,则可能是个例外。

如果接口方法的实现没有 public 范围, TBar 范围内的 之外也可能存在差异。所有界面方法都是公开,因此如果特定方法的实施是私有受保护,那么 TBar 参考不能用来调用那个接口方法,例如只能用 IFoo

但是在你的情况下,整个接口被委托,因此 TBar 类本身就不存在这些方法。

您应该能够将self输入到所需的界面类型,但是没有必要引发 QueryInterface AddRef / 释放电话和其他所需的脚手架,只是为了实现这一点。

已经引用了您需要的界面。您可以直接调用它:

procedure TBar.ObjectMethod
begin
  fFoo.InterfaceMethod;  // fFoo holds the delegated interface so if the TBar
                         //  needs to call the delegated interface, just call it!
end;

同样适用于您的 TSampleDbEngine ,它同样已经具有对委托接口的必需引用:

procedure TSampleDbEngine.Execute;
begin
  fSqlMaker.GetSqlStatement;
end;

在下面的评论中,您描述了您真正关心的是为您的实现提供一致的接口[原文如此],而无需用户担心他们是否具有对象或对象实现的接口的引用。

我认为这表明对界面所代表的内容存在误解。

如果代码只有一个对象引用,那么它只能调用为该类型的对象定义的方法。

o: TFoo;

o.ObjectMethod;    // This is OK, o is a TFoo reference
o.Bar;             // This is NOT OK.  o is NOT an IFoo reference

如果 TFoo 直接并公开实施 IFoo 界面所需的 Bar 方法,则会出现混淆:

TFoo = class(TObject, IFoo)
public
  procedure Bar;
end;

o: TFoo;

o.Bar;   // This is fine, but o is still a TFoo. 
         //  IFoo is completely irrelevant here,
         //  it may as well not exist.

这可能会使您看起来正在使用 TFoo IFoo 界面。但你不是。

简单地说,满足该接口的方法恰好可以通过对实现对象的对象引用来公开访问。这可以通过以下事实来说明:如果该方法是私有,则接口仍然满足并且可以通过接口引用使用,但不再可以通过对象引用访问:

TFoo = class(TObject, IFoo)
private
  procedure Bar;
end;

o: TFoo;
i: IFoo;

i := o as IFoo;
i.Bar;   // This is fine.  Interface methods are always public
o.Bar;   // Once again, this will NOT compile because TFoo.Bar is now private

显然,对于接口引用,引用的使用者不知道也不关心该接口是由对象直接实现还是已被委派。

请注意,这与对象可能实现 TWO (或更多)不同接口的情况没有什么不同。您不能使用对另一个接口的引用来调用一个接口的方法。

您必须获取正确的引用类型才能使用该类型的引用。

所以你真正的问题是你的实现允许用户从一开始就获得错误类型的引用。

据推测,你有一个构造函数当然会产生一个对象引用,但是假设该对象的使用者应该将其转换为接口引用,但是这个假设不是强制执行的(或者是强制执行的,如实现的那样)。

您可以帮助通过不提供公共构造函数来强制执行该操作,而是提供一个只生成接口引用的工厂方法,并在有人错误地尝试直接实例化该类时抛出异常:

type
  IFoo = interface
    procedure Bar;
  end;

  TFoo = class(TInterfacedObject, IFoo)
  private
    procedure Bar;
    constructor InternalCreate;
  public
    class function NewFoo: IFoo;
    constructor Create;
  end;


procedure TFoo.Bar;
begin

end;


constructor TFoo.InternalCreate;
begin
  inherited Create;
end;


constructor TFoo.Create;
begin
  raise ENotSupportedException.Create('Do not create instances of TFoo.  Use the factory method(s) provided');
end;


class function TFoo.NewFoo: IFoo;
begin
  result := TFoo.InternalCreate;  // We can call our own private, internal constructor
end;

现在,您的类的使用者只能获得对实现该接口的对象的接口引用,因此无法进入一个棘手的情况,他们会被类上的方法与接口上的方法和任何方法混淆。由于方法可见性,界面委派或其他任何他们真正不应该关注的事情而可能存在的差异。

一般来说: 不要尝试 - 并且不要鼓励代码的使用者尝试 - 将接口和对象引用混合到同一个对象。

除了这些问题之外,还有其他更严重的问题,您可能会遇到对象引用缺乏引用计数。