如果我有某个东西的各种子类,以及一个对这些子类的实例进行操作的算法,并且如果算法的行为根据实例的特定子类而略有不同,那么最常见的面向对象的方法是使用虚拟方法。

例如,如果子类是 DOM 节点,并且算法是插入子节点,则该算法会有所不同,具体取决于父节点是 DOM 元素(可以有子节点)还是 DOM 文本(不能有子节点):所以 insertChildren 方法可以是虚拟的(或抽象的) DomNode 基类,并在每个中以不同的方式实现 DomElementDomText 子类。

另一种可能性是为实例提供一个公共属性,其值可以读取:例如,算法可能会读取 nodeType 的财产 DomNode 基类;或者再例如,您可能有不同类型(子类)的网络数据包,它们共享一个公共数据包标头,您可以读取数据包标头来查看它是什么类型的数据包。

我没有太多使用运行时类型的信息,包括:

  • isas C# 中的关键字
  • 贬低
  • dot net 中的 Object.GetType 方法
  • typeid C++ 中的运算符

当我添加一个取决于子类类型的新算法时,我倾向于向类层次结构中添加一个新的虚拟方法。

我的问题是,什么时候适合使用运行时类型信息而不是虚拟函数?

有帮助吗?

解决方案

当没有其他办法的时候。虚拟方法始终是首选,但有时它们就是无法使用。发生这种情况的原因有多种,但最常见的原因是您没有要使用的类的源代码,或者无法更改它们。当您使用遗留系统或闭源商业库时,通常会发生这种情况。

在 .NET 中,您还可能必须动态加载新程序集(例如插件),并且通常没有基类,但必须使用鸭子类型之类的东西。

其他提示

在 C++ 中,除了其他一些模糊的情况(主要涉及较差的设计选择)之外,RTTI 是一种实现所谓的方法 多种方法.

这种结构(“is”和“as”)对于 Delphi 开发人员来说非常熟悉,因为事件处理程序通常将对象向下转换为共同的祖先。例如事件 OnClick 传递唯一的参数 Sender:TObject 无论对象的类型如何,无论是 TButton、TListBox 还是任何其他。如果你想了解更多关于这个对象的信息,你必须通过“as”来访问它,但为了避免异常,你可以在之前用“is”检查它。这种向下转型允许对象和方法的设计类型绑定,而这在严格的类类型检查中是不可能实现的。想象一下,如果用户单击 Button 或 ListBox,您想要执行相同的操作,但如果它们为我们提供了不同的函数原型,则不可能将它们绑定到同一过程。

在更一般的情况下,对象可以调用通知该对象已更改的函数。但提前它让目的地有可能“亲自”认识他(通过现状),但不一定。它通过将 self 作为所有对象的最常见祖先(Delphi 中的 TObject )传递来实现此目的

如果我没记错的话,dynamic_cast<> 取决于 RTTI。当对象通过 void 指针传递时(无论出于何种原因),一些模糊的外部接口也可能依赖于 RTTI 可能发生)。

话虽这么说,我在 10 年的专业 C++ 维护工作中还没有见过 typeof() 。(幸运的是。)

对于运行时类型检查可以的情况,您可以参考更有效的C#。

第 3 项。使用运行时类型检查专门使用通用算法

您可以通过简单地指定新类型参数来轻松重复使用通用物。具有新类型参数的新实例化意味着具有相似功能的新类型。

所有这些都很棒,因为您编写的代码少。但是,有时更通用意味着不利用更具体的算法,但显然是优越的。C#语言规则考虑到这一点。您所需要的只是认识到当类型参数具有更大的功能,然后编写特定代码时,您的算法可以更有效。此外,创建指定不同约束的第二个通用类型并不总是可行。通用实例基于对象的编译时类型,而不是运行时类型。如果您没有考虑到这一点,则可能会错过可能的效率。

例如,假设您编写一个类,该类对通过 IEnumerable<T> 表示的项目序列提供逆序枚举。为了向后枚举它,您可以迭代它并将项目复制到具有索引器访问(如 List<T>)的中间集合中,然后使用索引器访问向后枚举该集合。但是,如果您的原始 IEnumerable 是 IList,为什么不利用它并提供更高效的方式(无需复制到中间集合)来向后迭代项目。所以基本上它是一个我们可以利用的特殊功能,但仍然提供相同的行为(向后迭代序列)。

但一般来说,您应该仔细考虑运行时类型检查并确保它不违反里氏替换原则。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top