【问题标题】:Does it violate encapsulation for interface property accessors to be public?它是否违反了接口属性访问器公开的封装?
【发布时间】:2012-06-06 20:24:04
【问题描述】:

当我们在 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 是具体类的内部结构,不应暴露。

【问题讨论】:

  • 这听起来像是在咆哮。你有什么问题?
  • 哪一点让您感到困惑?这对我来说非常有意义。
  • -1 用于在您已经获得三个答案之后完全改变问题的性质。我反对你的说法,即我的编辑“改变了方向”。我的编辑将您的两个问题放入标题中,以便标题是一个正确的问题。 Yours 是改变方向的编辑。
  • 对那个 Rob 感到抱歉,但问题不在于“为什么我不能从接口访问实例成员”,我已经知道了。问题是“接口应该公开属性封装的内部方法吗?”。文字真的很混乱,我的英语也很烂。
  • 接口中的属性是一个丑陋的 hack IMO。明确定义底层方法肯定是必要的,但它错过了它们的全部意义(为了简化事情)。此外,必须在接口的每个实现上重新定义属性也是荒谬的。您最终在接口中有 2 个方法定义和 1 个属性定义,再加上类中的字段本身。单个字段的 7 个定义,以及 getter 和 setter 本身的实现。如此简单的任务需要大量的代码。

标签: delphi oop properties interface


【解决方案1】:

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

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

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

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


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

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

【讨论】:

  • “如果您更改属性定义,则需要重新编译使用该类的任何代码,以便编译器可以生成用于访问该属性的新代码”。说得好,这就是为什么我认为始终使用方法访问器而不是使用字段(甚至是简单的值属性)来定义属性是一种很好的做法,因为将来不需要重新定义属性。
  • 是的,关于与接口属性无关的具体属性是绝对正确的,这确实具有误导性。总之,我觉得有点矛盾。 “Delphi 方式”:保持你的 getter/setter 私有,只发布属性。比良好的 OOP 实践说“程序到接口,而不是具体类”。所以,你不能保护你的 getter 和 setter。
  • 但是对于接口,没有什么可以保护它们免受的影响。它们不仅仅是接口中的实现detail。它们是如何使用该类的一个组成部分。属性(如果存在)只不过是接口中的事后考虑,因为它们不像方法那样普遍可用。
【解决方案2】:

但是,接口强制使用 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;

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

  • 类具有“访问说明符”,这将始终受到尊重
    访问类类型时;
  • 接口没有“访问说明符”,所有方法在访问该接口类型时都会被认为是公共的;

【讨论】:

  • 接口不是用来替代 getter 和 setter 方法吗?这就是为什么 getter 和 setter 被实现为私有并且只发布属性的原因。所有的 VCL 都是这样设计的,它是一种对类用户说的方式:“嘿,用户,只要使用这个属性,无论我是否使用 getter 方法,都是我的事,不是你的事”。当我说打破封装时,我的意思是接口暴露了实现内部。
  • 但它们显然不是内部的,因为它们根据定义是公开的。由于所有接口属性都由方法支持,因此您不会通过说特定属性由方法支持来向消费者透露任何秘密实现细节。
猜你喜欢
  • 1970-01-01
  • 2021-12-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多