5

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

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

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

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

  • C#中的isandas关键字
  • 垂头丧气
  • 点网中的 Object.GetType 方法
  • typeidC++中的运算符

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

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

4

5 回答 5

5

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

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

于 2009-10-05T14:46:36.800 回答
3

在 C++ 中,在其他一些晦涩的情况(主要处理劣质设计选择)中,RTTI 是一种实现所谓的多方法的方法

于 2009-10-05T14:45:05.993 回答
1

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

在更一般的情况下,一个对象可以调用一个函数来通知该对象已经改变。但事先它让目的地有可能“亲自”(通过现状)了解他,但不一定。它通过将 self 作为所有对象的最常见祖先(在 Delphi 案例中为 TObject)传递来做到这一点

于 2009-10-05T15:09:12.030 回答
0

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

话虽如此,在 10 年的专业 C++ 维护工作中,我还没有在野外看到过 typeof()。(幸运的是。)

于 2009-10-05T14:45:29.367 回答
0

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

Item 3. 使用运行时类型检查专门化通用算法

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

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

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

但总的来说,您应该仔细考虑运行时类型检查,并确保它不违反 Liskov Substituion 原则。

于 2009-10-05T14:51:57.913 回答