动态分发

程序总是使用静态调度。对于动态调度,用 method 替换 proc 关键字:

  1. type
  2. Expression = ref object of RootObj ## abstract base class for an expression
  3. Literal = ref object of Expression
  4. x: int
  5. PlusExpr = ref object of Expression
  6. a, b: Expression
  7.  
  8. # 注意:'eval'依赖于动态绑定
  9. method eval(e: Expression): int {.base.} =
  10. # 重写基方法
  11. quit "to override!"
  12.  
  13. method eval(e: Literal): int = e.x
  14. method eval(e: PlusExpr): int = eval(e.a) + eval(e.b)
  15.  
  16. proc newLit(x: int): Literal = Literal(x: x)
  17. proc newPlus(a, b: Expression): PlusExpr = PlusExpr(a: a, b: b)
  18.  
  19. echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4)))

请注意,在示例中,构造函数 newLitnewPlus 是procs,因为它们使用静态绑定更有意义,但 eval 是一种方法,因为它需要动态绑定。

注意: 从Nim 0.20开始,要使用多方法,必须在编译时明确传递 —multimethods:on

在多方法中,所有具有对象类型的参数都用于分发:

  1. type
  2. Thing = ref object of RootObj
  3. Unit = ref object of Thing
  4. x: int
  5.  
  6. method collide(a, b: Thing) {.inline.} =
  7. quit "to override!"
  8.  
  9. method collide(a: Thing, b: Unit) {.inline.} =
  10. echo "1"
  11.  
  12. method collide(a: Unit, b: Thing) {.inline.} =
  13. echo "2"
  14.  
  15. var a, b: Unit
  16. new a
  17. new b
  18. collide(a, b) # output: 2

如示例所示,多方法的调用不能有歧义:collide2比collide1更受欢迎,因为解析是从左到右的。因此 Unit,ThingThing,Unit 更准确。

性能说明: Nim不会生成虚函数表,但会生成调度树。这避免了方法调用的昂贵间接分支并启用内联。但是,其他优化(如编译时评估或死代码消除)不适用于方法。