4

当我们在 Delphi 中设计一个类时,通常我们有私有字段(成员)、私有 setter 和 getter 方法以及一个公共属性。从课堂之外,只能通过公共财产访问该数据;该类的用户甚至不知道存在 getter 方法。

所以 getter 和 setter 方法封装了实例成员,而属性封装了 getter 和 setter 方法。

但是,在定义接口时,我们会公开这些方法:

ICounter = interface
  // I wouldn't want to specify these 2 methods in the interface, but I'm forced to
  function GetCount: Integer;
  procedure SetCount(Value: Integer);

  property Count: Integer read GetCount write SetCount;
end;

实现具体类:

TCounter = class(TInterfacedObject, ICounter)
private
  function GetCount: Integer;
  procedure SetCount(Value: Integer);
public
  property Count: Integer read GetCount write SetCount;
end

使用它:

var
  Counter: ICounter;
begin
  Counter := TCounter.Create;
  Counter.Count := 0; // Ok, that's my public property

  // The access should me made by the property, not by these methods
  Counter.SetCount(Counter.GetCount + 1);
end;

如果属性封装了getter/setter私有方法,这不是违规吗?getter 和 setter 是具体类的内部结构,不应暴露。

4

2 回答 2

8

方法是与接口交互的主要方式。接口上的属性是 Delphi 特有的扩展;它们只是为底层方法提供语法糖。没有其他语言 由于接口中的方法根据定义是公共的,因此它们不被属性封装。您不会通过显示属性由方法支持来揭示任何实现细节,因为在接口中,属性始终由方法支持,并且方法始终是公共的。如果一开始就没有封装,就不能违反封装。

您的示例具体类具有误导性。首先,在那里定义的属性与接口中定义的属性绝对没有联系。您可以将其定义为只读,使其直接访问数据成员,使其私有,或以任何其他方式与接口版本不同,包括完全删除它,它不会影响接口的用户,进一步借用相信接口中重要的是方法而不是属性的概念。编译器将接口属性的任何使用直接转换为相应接口方法之一的使用,这些方法已经是公共的。从来没有就此事咨询过实施对象。

其次,类上的可见性说明符是不相关的。无需将方法设为私有,因为它们已经在接口上公开。但是,将它们设为私有并不是一个坏主意,因为它鼓励通过接口正确使用类。

您可能会抱怨接口的访问器方法应该能够是私有的,但这与要求接口方法通常能够是私有的一样,这是没有意义的。一个不能被调用的方法显然不是接口的一部分。回想一下,任何支持 COM 的语言都可以使用接口,即使是没有属性概念的语言,例如 C 和 C++。这些语言也需要能够调用访问器方法。如果方法在某种程度上是私有的,那么接口将无法在这些语言中工作。


当 Delphi 类的属性引用一个字段时,该细节实际上是该类的面向公众的接口的一部分。任何使用该属性的代码都知道该属性只是该字段的别名(即使代码的作者不知道这一点)。如果您更改属性定义,则需要重新编译使用该类的任何代码,以便编译器可以生成用于访问该属性的新代码。

当属性需要由方法支持时,您无法再真正更改属性定义。只有实现可以更改,因此接口的使用者不需要重新编译,因为您选择按需计算属性而不是从存储的字段中读取。

于 2012-06-01T15:24:11.720 回答
3

但是,接口强制使用 Getter/Setter 方法:

因为接口没有实例,所以不能存储数据。

除此之外,Getter 和 Setter 方法通常是私有的。在界面上定义它们使界面用户可以访问它们。它造成了一些混乱:

接口不指定成员的可见性。由实现该接口的类来定义哪些属性和方法是可见的以及它们对谁是可见的(受保护的、公共的或已发布的)。

如果您将仅通过您自己的类/模块使用您的接口,那么更改实现类中的可见性没有问题,尽管这不是一个好习惯。

现在,我将如何设置计数:Counter.Count := 0 或 Counter.SetCount(0)?这不是破坏封装吗?

您可以通过两种方式设置 count 属性。如果设置它:

Counter.Count := 0

访问方法将以相同的方式调用。并且没有封装被破坏。

编辑:

让我举一个例子来说明没有什么会破坏,但可能会有不同的行为:

鉴于您的 ICounter 接口的此实现:

  TMyCounter = class(TInterfacedObject, ICounter)
  private
    FCount: Integer;
    function GetCount: Integer;
    procedure SetCount(Value: Integer);
  public
    property Count: Integer read GetCount write SetCount;
  end;

implementation

function TMyCounter.GetCount: Integer;
begin
  Result := FCount;
end;

procedure TMyCounter.SetCount(Value: Integer);
begin
  FCount := Value;
end;

您的财产的一些可能用途是:

var
  c: TMyCounter;
  ic: ICounter;
begin
  c := TMyCounter.Create;
  try
    //c.SetCount(1); //won't compile, since the setter is private
    ICounter(c).SetCount(1); //it is ok, because the interface method is public
    c.Count := C.Count + 1; //it is ok, cause the SetCount acessor will be invoked
    if Supports(c, ICounter, ic) then
      ShowMessage(IntToStr(ic.GetCount));
  finally
    c := nil;
  end;

因此,我们可以得出结论:


  • 类具有“访问说明符”,在访问类类型时将始终遵守这一点;
  • 接口没有“访问说明符”,所有方法在访问接口类型时都会被认为是公共的;
于 2012-06-01T15:33:27.703 回答