如果我有各种类的子类,以及对这些子类的实例进行操作的算法,并且如果算法的行为根据实例的特定子类略有不同,那么最常用的面向对象的方法这是使用虚拟方法。
例如,如果子类是DOM节点,并且算法要插入子节点,则该算法根据父节点是DOM元素(可以有子节点)还是DOM文本(不能):所以insertChildren
方法可能是DomNode
基类中的虚拟(或抽象)方法,并且在每个DomElement
和DomText
子类中实现不同。
另一种可能性是为实例提供一个公共属性,可以读取其值:例如,算法可能会读取nodeType
基类的DomNode
属性;或者另一个例子,您可能有不同类型(子类)的网络数据包,它们共享一个公共数据包标头,您可以读取数据包标头以查看它是什么类型的数据包。
我没有太多使用运行时类型信息,包括:
is
和as
个关键字
typeid
运算符当我添加一个取决于子类类型的新算法时,我倾向于在类层次结构中添加一个新的虚方法。
我的问题是,何时使用运行时类型信息而不是虚函数?
答案 0 :(得分:5)
当没有别的办法时。虚拟方法总是首选,但有时它们无法使用。有几个原因导致这种情况发生,但最常见的原因是您没有要使用的类的源代码,或者您无法更改它们。当您使用遗留系统或使用闭源商业库时,通常会发生这种情况。
在.NET中,你可能还需要动态加载新的程序集,比如插件,你通常没有基类,但必须使用像duck typing这样的东西。
答案 1 :(得分:3)
在C ++中,在一些其他模糊的案例中(主要处理较差的设计选择),RTTI是一种实现所谓multi methods的方法。
答案 2 :(得分:1)
这种结构(“是”和“as”)对于Delphi开发人员来说非常熟悉,因为事件处理程序通常会将对象转发给共同的祖先。例如,事件OnClick传递唯一的argurment Sender:TObject,无论对象的类型是什么,无论是TButton,TListBox还是其他任何类型。如果您想了解有关此对象的更多信息,则必须通过“as”访问它,但为了避免异常,您可以在之前使用“is”进行检查。这种向下转换允许通过严格的类类型检查无法实现的对象和方法的设计类型绑定。想象一下,如果用户单击Button或ListBox,您想要执行相同的操作,但如果它们为我们提供了不同的函数原型,则无法将它们绑定到相同的过程。
在更一般的情况下,对象可以调用一个函数来通知对象例如已经改变。但事先它让目的地“亲自”(通过as和is)了解他,但不一定。它通过将self作为所有对象的最常见祖先(Delphi案例中的TObject)来实现这一点
答案 3 :(得分:0)
dynamic_cast<>,如果我没记错的话,取决于RTTI。当对象通过void指针传递时,一些模糊的外部接口也可能依赖于RTTI(无论出于何种原因 可能发生)。
话虽这么说,我在10年的专业C ++维护工作中还没有看到typeof()。 (幸运。)
答案 4 :(得分:0)
对于运行时类型检查正常的情况,您可以参考更有效的C#。
项目3.专门化通用算法 使用运行时类型检查
您可以轻松地重复使用泛型 只需指定新类型参数。 新类型的新实例化 参数表示具有的新类型 类似的功能。
这一切都很棒,因为你写的 更少的代码。但是,有时候是 更通用意味着不采取 一个更具体的优势,但 算法明显优越。 C# 语言规则将此考虑在内。 所需要的只是让你认识到 你的算法可以更多 高效时的类型参数 有更大的能力,然后到 写那个特定的代码。此外, 创建第二个泛型类型 指定不同的约束 并不总是有效。通用 实例化是基于 编译时类型的对象,和 不是运行时类型。如果你没有 考虑到这一点,你可以错过 可能的效率。
例如,假设您编写了一个类,该类在通过IEnumerable< T>表示的项目序列上提供反向顺序枚举。为了向后枚举它,您可以迭代它并将项目复制到具有索引器访问权限的中间集合中,例如List< T>然后使用索引器访问向后枚举该集合。但是如果你的原始IEnumerable是IList,为什么不利用它并提供更高效的方式(不复制到中间集合)来向后迭代项目。所以基本上它是一个特殊的我们可以利用但仍然提供相同的行为(迭代序列向后)。
但总的来说,你应该仔细考虑运行时类型检查,并确保它不违反Liskov Substituion Principle。