与 Self 相同的后代 class' 方法的变量是否应该可以访问其祖先的受保护方法?

Should a descendant class' method's variable that is identical to Self, have access to its ancestor's protected methods?

这个问题源于使用方法链(流畅的界面)时出现的一个问题,我想这是它可能成为问题的唯一原因之一。

为了说明,我将使用一个使用方法链的示例:

单位A:

TParent = class
protected
  function DoSomething: TParent;
end;

B单元:

TChild = class(TParent)
public
  procedure DoAnotherThing;
end;

implementation

procedure TChild.DoAnotherThing;
begin
  DoSomething.DoSomething
end;

我想保护 DoSomething 过程并仅对 class 后代可见。

这不会编译,抛出

cannot access protected symbol TParent.DoSomething

因为 DoSomething returns TParent 和后续的 DoSomething 调用是从另一个单元中的 TParent 对象发出的(因此保护启动并且函数不可访问)。 (感谢 David Heffernan 的解释)

为了将其简化为最基本的本质,TChild 中不可能有像 TParent(Self).DoSomething 这样的东西 class.

我的问题:

由于编译器确实知道正在从子 class 中访问 Self 参数的副本,是否存在访问祖先受保护方法的能力会破坏封装的情况?我只是在谈论从内部 后代的 class 方法中取消引用类型转换的 Self 。我知道在这个 class 之外,那个参数当然不应该访问祖先的受保护方法(在另一个单元中)。

同样,简而言之:当一个与 Self 参数相同的变量在它自己的 class 方法之一中被取消引用时,编译器允许它访问其父级的方法是否不安全受保护的方法(就像 Self 参数本身一样)?

这是一个非常理论化的问题,但如果编译器允许这样做,它是否会对编译代码或封装产生任何负面影响,我会很感兴趣。

谢谢。

嗯,child 可能相同,但也可能不同。

考虑对您的示例稍作扩展。

TParent = class
protected
  function DoSomething: TParent;
end;

和单元 2

TChild = class(TParent)
public
  procedure DoAnotherThing;
end;

TChild2 = class(TParent)
public
  procedure DoAnotherThing;
  function DoSomething: Child2;
end;

implementation

procedure TChild.DoAnotherThing;
begin
  DoSomething.DoSomething
end;

procedure TChild2.DoAnotherThing;
var
  p : TParent;
begin
  p := DoSomething;
  p.DoSomething;
end;

现在DoSomething返回的值肯定是一个TParent。它可能是一个 TChild 或者实际上是一个 TChild2 或者甚至是你知之甚少的东西(除了它是 TParent 的后代)。

如果返回值确实是一个 TChild,那么访问当然没有问题——它知道如何处理自己的数据类型,但如果不是,那么它无权访问其他的受保护函数 object。如果返回值是一个 TChild 你可以做你想做的,像这样

procedure TChild.DoAnotherThing;
var
  p : TParent;
begin
  p := DoSomething;
  if p is TChild then (p as TChild).DoSomething;
end;

protected 意味着您可以在自己的实例中访问这些方法。这并不意味着您可以在属于您派生类型的任何其他实例上访问这些方法。

您可以在 TChild 中调用 DoSomething 的原因是因为只有 Self 可以访问其祖先的受保护成员。

事实上,在这种特殊情况下,DoSomething 方法 equals Self 的结果无法由编译器求值(某些静态代码除外)分析我怀疑那里的任何 OOP 语言编译器。

C++ 通过使 TChild 成为 TParent 的朋友 class 从而使其能够访问这些方法来解决这个问题。

在 Delphi 中,如果将两个 class 都放入同一个单元中,您将获得该功能。如果您想将两个 class 保留在它们自己的单元中,您仍然可以通过声明 "cracker class" 来使用该功能。只需创建一个继承自 TParent 的 class,并将其放入与 TChild 相同的单元中。然后您可以将 DoSomething 的结果转换为 class 并访问 TParent.

的受保护方法
type
  TChild = class(TParent)
  public
    procedure DoAnotherThing;
  end;

implementation

type
  TParentAccess = class(TParent);

procedure TChild.DoAnotherThing;
begin
  TParentAccess(DoSomething).DoSomething;
end;

更新 6.12.2017:

您还可以在 TChild class 中添加一个方法来模仿 "friend status"(感谢 Ken Bourassa)。

type
  TChild = class(TParent)
  private
    type
      TParentAccess = class(TParent);
    function DoSomething: TParentAccess; inline;
  public
    procedure DoAnotherThing;
  end;

implementation

function TChild.DoSomething: TParentAccess;
begin
  Result := TParentAccess(inherited DoSomething);
end;

procedure TChild.DoAnotherThing;
begin
  DoSomething.DoSomething;
end;

第三种可能性是使用 class 帮助程序使该方法可访问。这样做的好处是易于重用,因为您只需将辅助单元添加到您拥有 TParent 的 child 并需要访问的任何单元。

type
  TParentHelper = class helper for TParent
  public
    function DoSomething: TParent; inline;
  end;

implementation

function TParentHelper.DoSomething: TParent;
begin
  Result := inherited DoSomething;
end;