防止可能揭示隐藏属性的类型转换?

时间:2014-07-01 21:15:42

标签: delphi delphi-xe

这个问题与我之前提出的问题有关:How do I prevent access to the Items property in TCustomListView?

我在上面链接的问题中发布了我的答案。基本上我欺骗了我的TMyCustomListView派生组件使用了一个受保护的不同Items类,因此在设计时或运行时不可见或无法访问。

我发现了一个弱点,但是你仍然可以对它进行类型转换以显示原始的listview Items属性。

TListView(MyListView1).Items.Add.Caption := 'test';

我认为我的组件的任何潜在用户都不太可能知道该组件是从TMyCustomListView派生的,以及他们是否需要将类型转换为列表视图。但是因为我不想访问原始的listview Items属性,因为无论如何用户都没有必要或好处访问它,我关于这个主题的最新问题是是否可以阻止我的组件来自typecasted?

谢谢。

3 个答案:

答案 0 :(得分:4)

您组件的每个潜在用户都可以知道它来自TMyCustomListView,因为它在您的发行版中包含的源文件中就是如此。 (如果你不包括来源,那么首先,你要感到羞耻,其次,他们可以只检查ClassParent。)

但不要担心。为其他开发人员隐藏所有内容并不是你的工作。想一想:你是开发者; 想要隐藏一切吗?当然不是。当您使用其他人的代码(包括Embarcadero' s)时,您有时会得出结论,您比原作者更了解如何解决您的问题,并且您希望能够访问将要发布的内容你解决了你的问题。同样,您的代码的消费者有时会比您更了解。假设您的代码考虑了所有可能的用途,这是傲慢的。

需要进行类型转换是代码气味。任何需要输入您的对象以使用它们的人都会知道他们正在做一些粗略的事情,但如果他们这样做了,你可以假设它已经理解它&& #39;它不是理想的解决方案,并且可能会在以后的代码版本中出现问题。当您发布新版本时,请不要对内部进行更改以故意破坏现有代码,但同时,不要担心对内部进行合法必要的更改以制作产品的其余部分更好。

您的目标应该是确保代码的消费者不需要做粗略的事情来解决他们的问题。如果他们这样做,那么请注意,因为那些显然是您的客户所在的地方。需要得不到满足。

最终,没有办法隐藏其他开发者的一切。通过良好的界面展示你能做到的事情,并且不要过分担心会弄乱内部人员。

答案 1 :(得分:4)

简答

继承的全部要点是子类必须看起来像它们的祖先。您只能添加功能 - 而不是删除它。

所以,不能做你想做的事。

然而,通过一些工作,您可以实现合适的封装效果。您需要使用合成而不是继承。 (建议:做一些关于“赞成组合而非继承”的研究。)


长答案

如果标准列表视图功能对组件的用户没有任何用处,那么您无法真正说出组件列表视图。 is-a 关系正是继承所代表的。

在处理对象时,基本上有两种方法可以重用代码:

  • 继承其他类:其中提供了 all 祖先类中的所有功能。 (强调所有,因为你甚至得到了你不想要的功能。)
  • 使用其他对象:您对另一个对象的引用,并要求它为您工作。

因此,不要继承列表视图,而应考虑使用合成。这个方法非常类似于你自己在其他问题中的答案,只需改变你继承的祖先。

TMyCustomListView = class(TComponent)
private
  FListView: TListView;
public
  constructor Create(AOwner: TComponent); override;
end;

constructor TMyCustomListView .Create(AOwner: TComponent); override;
begin
  inherited Create(AOwner);
  FListView := TListView.Create(Self);
end;

当然,您会发现TListView已支持您希望列表视图支持的一大堆功能。您想要公开的任何此类功能,只需重定向到内部列表视图即可。 E.g。

procedure TMyCustomListView.AddItem(Item: String; AObject: TObject);
begin
  FListView.AddItem(Item, AObject);
end;

事实证明,因为你不再继承,所以你不应该支持列表视图的界面。因此,您现在可以自由地进行更适合您组件的更改。

procedure TMyCustomListView.AddItem(AItemCaption: String;
    AObject: TMySpecialListViewObject; AItemHint: String);
begin
  //By forcing a specific kind of object to be added to your list view,
  //you know that anything added will be of an appropriate type.
  //Meaning that you can rely on using TMySpecialListViewObject
  //attributes in other places in your component.

  FListView.AddItem(Item, AObject);

  //Of course you can also do other things before or after adding to
  //the internal list view.
end;

优点和缺点

是的,很明显,使用组合而不是继承是更多的工作:

  • 您不会自动获取内部对象的功能。
  • 您必须明确添加所需内容,将调用重定向到内部对象。
  • 您可能需要拦截事件以跟踪内部对象发生的情况。
  • 如果您的内部对象没有暴露您需要的所有内容,有些事情可能会变得更难以支持。 (对于许多共享相同单元的VCL类尤其如此,可以访问类似于“朋友”类的私有成员。)

但优点是:

  • 与缺点中的第2点一样:您必须明确添加所需内容。但在这种情况下意味着你​​不要坐在你不想要的东西上。
  • 更好的封装。
  • 您的类更易于使用,因为它具有较少的方法/属性供用户理解。
  • 从长远来看,您的代码将更易于维护,因为您不必担心有人“错误地”使用您的课程。
  • 您的课程往往更“轻量级”,因为它们不会因未使用的功能而膨胀。
  • 轻量级课程更容易测试。

这些优点基本上是为什么它被认为是“有利于遗传而不是作曲”的好设计实践。

但与软件开发中的所有内容一样:您必须权衡利弊,并选择最适合每种情况的方法。有时即使一种类型真的应该从另一种类型继承,你也可以选择在任何情况下继续这样做。

附注

在示例中,我演示了继承自TComponent。但是,如果您希望允许其他人以TWinControlTControl与您的组件进行互动,则必须相应地更改继承。

如果您继承自TWinControl,则需要确定哪些获胜控制功能可以重定向到内部列表视图,以及最合适的方式。

E.g。您可以覆盖CreateWindowHandle以简单地将句柄设置为内部列表视图的句柄。或者您可以使用完整的消息处理,并决定将哪些消息传递给内部对象。

我不会在这里详细介绍,因为这个答案已经足够长了。

答案 2 :(得分:3)

无法删除后代类中的可见成员。如果你真的想呈现"看起来像X但没有Y属性的东西,"最简单的方法是创建一个包装X的新类,并使用简单的传递方法公开其大多数成员。