动态调度

单一与多重调度

选择调用方法的具体版本的决定可以基于单个对象或者对象的组合。前者被称为单一调度，并直接由常见的面向对象语言如C++，Java，Smalltalk、Objective-C、Swift、JavaScript和Python所支持。在这些和与其相类似的语言中，我们可以调用类似于语法的划分的方法。其参数是可选的。这被认为是发送一个名为divide，带有除数参数的信息来做除法。
相比之下，一些语言（例如Common Lisp、Dylan、Julia）采用基于操作数的组合的调度方法或函数；在除法情况下，被除数和除数的类型一起确定将执行哪个除法运算。这就叫做多重调度。

动态调度机制

语言可以用不同的动态调度机制来实现。 由语言提供的动态调度机制的选择在很大程度上改变了在给定语言内可用或最自然使用的编程范例。
通常，在类型语言中，会基于参数的类型（最常见地基于消息的接收者的类型）来执行调度机制。 这可能被称为“单类型动态调度”。具有较弱或无打字系统的语言通常携带调度表作为每个对象的对象数据的一部分。这允许实例行为因为每个实例可以将给定消息映射到单独的方法。而有些语言提供混合方法。
动态调度总会产生开销，因此一些语言为特定方法提供静态调度。

C++实现

C ++使用早期绑定，并提供动态和静态调度。 调度的默认形式是静态的。 为了获得动态分派，程序员必须将一个方法声明为virtual。
C ++编译器通常使用称为虚拟表（vtable）的数据结构来实现动态调度，该表定义给定类（C ++本身没有vtable的概念）的消息到方法映射。 然后，该类型的实例将存储指向此表的指针作为其实例数据的一部分。 当使用多重继承时，这是复杂的因为C ++不支持后期绑定，C ++对象中的虚拟表不能在运行时修改，这将调度目标的潜在集限制为在编译时选择的有限集。
类型重载不会在C ++中产生动态分派，因为语言将消息参数的类型视为正式消息名称的一部分。 这意味着程序员看到的消息名不是用于绑定的正式名称。

Go和Rust实现

在Go和Rust中使用了一种更多样化的早期绑定。 Vtable指针会携带对象引用作为一种'fat指针'（go中的接口，或Rust中的'trait对象'）。
这使得支持的接口与底层数据结构分离。 为了正确使用类型，每个编译库不需要知道所有的接口范围，只需要知道他们所需要的具体vtable布局即可。 代码可以将不同接口的同一块数据传递给不同的函数。 这种多功能性以每个对象引用的额外数据为代价，如果许多这样的引用被持久存储会出问题。

Smalltalk实现

Smalltalk使用基于类型的消息分派器。每个实例都有一个类型，其定义包含方法。当实例接收到消息时，调度器在消息到方法映射中查找该类型的相应方法，然后调用该方法。
因为一个类型可以有一个基本类型链，这个查找可能是非常昂贵的。 一个不成熟的Smalltalk机制的实现似乎比C ++有更高的开销，并且这种开销将会发生在对象接收的每个消息里。
真正的Smalltalk实现通常使用一种称为内联缓存的技术，这种技术使得方法调度非常迅速。内联缓存基本上存储调用站点的前一个目标方法地址和对象类（或者多对缓存的多个对）。缓存方法使用最常见的基于方法选择器的目标方法（或仅缓存缺失处理程序）来进行初始化。当在执行期间到达方法调用站点时，它只调用缓存中的地址。（在动态代码生成器中，此调用是直接调用，因为直接地址由缓存缺失逻辑负责回填）。被调用方法中的前序代码将缓存的类与实际对象类进行比较，如果它们不匹配，程序到缓存缺失处理程序分支中寻找正确的方法。一个快速实现可以具有多个高速缓存条目，并且它通常仅需要几个指令就可以在初始高速缓存未命中时执行正确的方法。常见的情况是一个缓存的类匹配，并且执行只会在该方法中继续。
外部缓存也可以通过使用对象类和方法选择器来实现在方法调用逻辑中的使用。在一个设计中，类和方法选择器被哈希并且用作到方法分派高速缓存表中的索引。
由于Smalltalk是一种反射语言，许多实现允许使用动态生成方法查找表的方法将单个对象变成对象。这允许在每个对象的基础上更改对象行为。一个被称为基于原型语言的语言类别已经从这里生根发芽，其中最着名的是Self和JavaScript。方法调度缓存的细心的设计甚至允许基于原型的语言具有高性能调度方法。
许多其他动态类型的语言，包括Python、Ruby、Objective-C和Groovy都使用类似的方法。