多分派

多分派或译多重派发（multiple dispatch）或多方法（multimethod），是某些编程语言的一个特性，其中的函数或者方法，可以在运行时间（动态的）基于它的实际参数的类型，或在更一般的情况下于此之外的其他特性，来动态分派。这是对单分派多态的推广， 那里的函数或方法调用，基于在其上调用方法的对象的派生类型，而动态分派。多分派使用一个或多个实际参数的组合特征，路由动态分派至实现函数或方法。 软件工程师通常把代码写进代码块中，代码块通常称作过程、函数、方法。代码通过被调用来执行，调用时将控制权传入函数中，当函数执行完成后将控制权返回给调用者。 函数名通常用来描述函数的目的。有时会将多个函数起同样的名称。比如同名函数在逻辑上处理相同的任务，但是操作在不同类型的输入值上。在这种情况下，无法仅仅通过函数名，来判断目标代码块。那么，函数的实际参数的个数和类型，也就被用来判断。 通常，单分派面向对象语言，在调用一个方法时，方法参数中一个参数会被特殊对待，并用来决定哪一个方法（如果有多个同名方法）会被调用。在许多语言中，这个特殊的参数是在语法上指明的，许多编程语言在调用方法时，把特殊参数放在小圆点（.）之前。例如 special.method(other, arguments, here)，这样 lion.sound() 将会发出狮吼，同时 sparrow.sound() 只会吱吱地叫。一般来说，对于面向对象的编程语言，这个小圆点之前的参数（上例中的lion和sparrow）被称为接收者。 相反，在实现了多分派的语言中，被调用的函数，即是那些参数个数一样多，并且类型也匹配的调用。在调用中并没有特殊参数，来决定那个方法被调用。也就是说，所有参数的运行时类型都参与分派。CLOS是早期和著名的多分派语言。 对于编译时间可以区分数据类型的编程语言，在交替（英语：Alternation (formal language theory)）（alternative）函数中进行选择，可以发生在编译时间，创建交替函数用于编译时间选择的活动，通常被叫做函数重载。 在有些编程语言中，这种数据类型的识别，可以被延后至运行时间（后期绑定（英语：Late binding））。交替函数的选择发生在运行时间，并依据动态确定的函数实际参数的类型。以这种方式选择交替实现的函数，通常被称为多方法。 可以通过例子更加清晰的区分多分派和单一分派。假想一个游戏，它有两种（用户可见的）物体：飞船和小行星。当两个物体要相撞的时候，程序需要依据什么物体要相撞而做不同的事情。 在具有多分派的Common Lisp语言中，可以在Common Lisp对象系统中如下这样实现： (defgeneric collide (x y)) (defclass asteroid () ()) (defclass spaceship () ()) (defmethod collide-with ((x asteroid) (y asteroid)) ;; deal with asteroid hitting asteroid ) (defmethod collide-with ((x asteroid) (y spaceship)) ;; deal with asteroid hitting spaceship ) (defmethod collide-with ((x spaceship) (y asteroid)) ;; deal with spaceship hitting asteroid ) (defmethod collide-with ((x spaceship) (y spaceship)) ;; deal with spaceship hitting spaceship ) 并且对其他方法也是类似的。没有使用显式测试和“动态转换”。 由于多分派的存在，方法要定义在类中并包含在对象中的传统想法，变得不再吸引人了，上述每个collide-with方法，都附属于两个不同的类而非一个类。因此方法调用的特殊语法，一般会消失，从而方法调用看起来完全就像正常的函数调用，并且方法被组织入泛化函数而非类中。 Julia有内建的多分派，并且它是语言设计的中心。Julia版本的例子如下： collide_with(x::Asteroid, y::Asteroid) = ... # deal with asteroid hitting asteroid collide_with(x::Asteroid, y::Spaceship) = ... # deal with asteroid hitting spaceship collide_with(x::Spaceship, y::Asteroid) = ... # deal with spaceship hitting asteroid collide_with(x::Spaceship, y::Spaceship) = ... # deal with spaceship hitting spaceship C#在版本4（2010年4月），使用关键字dynamic，介入了对动态多方法的支持。下面的例子展示多方法协同于在版本8（2019年9月）中介入的switch表达式。像很多其他静态类型的语言语言一样，C#还支持静态方法重载，Microsoft预期开发者在多数场景下会选用静态类型超过动态类型。dynamic关键字支持COM对象和动态类型的.NET语言的互操作。 class Program { static void Main() { Console.WriteLine(Collider.Collide(new Asteroid(101), new Spaceship(300))); Console.WriteLine(Collider.Collide(new Asteroid(10), new Spaceship(10))); Console.WriteLine(Collider.Collide(new Spaceship(101), new Spaceship(10))); } } static class Collider { public static string Collide(SpaceObject x, SpaceObject y) => ((x.Size > 100) && (y.Size > 100)) ? "Big boom!" : CollideWith(x as dynamic, y as dynamic); private static string CollideWith(Asteroid x, Asteroid y) => "a/a"; private static string CollideWith(Asteroid x, Spaceship y) => "a/s"; private static string CollideWith(Spaceship x, Asteroid y) => "s/a"; private static string CollideWith(Spaceship x, Spaceship y) => "s/s"; } abstract class SpaceObject { public SpaceObject(int size) => Size = size; public int Size { get; } } class Asteroid : SpaceObject { public Asteroid(int size) : base(size) { } } class Spaceship : SpaceObject { public Spaceship(int size) : base(size) { } } 输出： big-boom a/s s/s Groovy是通用的Java兼容/互用的JVM语言，它对立于Java，使用后期绑定/多分派。 /* Groovy implementation of C# example above Late binding works the same when using non-static methods or compiling class/methods statically (@CompileStatic annotation) */ class Program { static void main(String[] args) { println Collider.collide(new Asteroid(101), new Spaceship(300)) println Collider.collide(new Asteroid(10), new Spaceship(10)) println Collider.collide(new Spaceship(101), new Spaceship(10)) } } class Collider { static String collide(SpaceObject x, SpaceObject y) { (x.size > 100 && y.size > 100) ? "big-boom" : collideWith(x, y) // Dynamic dispatch to collideWith method } private static String collideWith(Asteroid x, Asteroid y) { "a/a" } private static String collideWith(Asteroid x, Spaceship y) { "a/s" } private static String collideWith(Spaceship x, Asteroid y) { "s/a" } private static String collideWith(Spaceship x, Spaceship y) { "s/s"} } class SpaceObject { int size SpaceObject(int size) { this.size = size } } @InheritConstructors class Asteroid extends SpaceObject {} @InheritConstructors class Spaceship extends SpaceObject {} 在不于语言定义或语法层次支持多分派的语言中，可能经常使用库扩展来增加多分派。 JavaScript和TypeScript不在语言语法层次上支持多方法，但可以通过库来增加多分派。例如，使用multimethod包，它提供了多分派、泛化函数的实现。JavaScript的动态类型版本： import { multi, method } from '@arrows/multimethod' class Asteroid {} class Spaceship {} const collideWith = multi( method([Asteroid, Asteroid], (x, y) => { // deal with asteroid hitting asteroid }), method([Asteroid, Spaceship], (x, y) => { // deal with asteroid hitting spaceship }), method([Spaceship, Asteroid], (x, y) => { // deal with spaceship hitting asteroid }), method([Spaceship, Spaceship], (x, y) => { // deal with spaceship hitting spaceship }), ) TypeScript有对应的静态类型版本。 可以使用库扩展来向Python增加多分派。例如，最早的模块multimethods.py，它为Python提供了CLOS风格的多方法而不用变更语言的底层语法或关键字。在功能上，这非常类似于CLOS例子，但是语法是常规Python的。 Guido van Rossum使用Python 2.4介入的修饰器（decorator），出品了多方法的具有简化了的语法的一个简单实现，他为此定义了multimethod修饰器。 multipledispatch采用的形式与之一致。 模块multimethod，采用了修饰器和Python 3.5介入的类型提示实现多方法： from multimethod import multimethod class Asteroid(): pass class Spaceship(): pass @multimethod def collide_with(x: Asteroid, y: Asteroid): '''deal with asteroid hitting asteroid''' print("asteroid hitting asteroid") @multimethod def collide_with(x: Asteroid, y: Spaceship): '''deal with asteroid hitting spaceship''' print("asteroid hitting spaceship") @multimethod def collide_with(x: Spaceship, y: Asteroid): '''deal with spaceship hitting asteroid''' print("spaceship hitting asteroid") @multimethod def collide_with(x: Spaceship, y: Spaceship): '''deal with spaceship hitting spaceship''' print("spaceship hitting spaceship") >>> a = Asteroid() >>> b = Spaceship() >>> collide_with(a, b) asteroid hitting spaceship 此外，还有PEAK-Rules包提供语法类似上述例子的多分派，它后来被替代为PyProtocols。Reg库也支持多分派和谓词分派。 C语言使用C Object System库，可以支持类似于CLOS的动态分派。它是完全可扩展的并且方法不需要任何的手工处理。动态消息（方法）通过COS分派器来分派，它比Objective-C更快。下面是使用COS的例子： #include <stdio.h> #include <cos/Object.h> #include <cos/gen/object.h> /* 类 */ defclass (Asteroid) /* 数据成员 */ endclass defclass (Spaceship) /* 数据成员 */ endclass /* 泛化函数 */ defgeneric (_Bool, collide_with, _1, _2); /* 多方法 */ defmethod (_Bool, collide_with, Asteroid, Asteroid) /* deal with asteroid hitting asteroid */ endmethod defmethod (_Bool, collide_with, Asteroid, Spaceship) /* deal with asteroid hitting spaceship */ endmethod defmethod (_Bool, collide_with, Spaceship, Asteroid) /* deal with spaceship hitting asteroid */ endmethod defmethod (_Bool, collide_with, Spaceship, Spaceship) /* deal with spaceship hitting spaceship */ endmethod /* 用例 */ int main(int argc, char *argv[]) { OBJ a = gnew(Asteroid); OBJ s = gnew(Spaceship); printf("<a,a> = %d\n", collide_with(a, a)); printf("<a,s> = %d\n", collide_with(a, s)); printf("<s,a> = %d\n", collide_with(s, a)); printf("<s,s> = %d\n", collide_with(s, s)); grelease(a); grelease(s); } Julia C# 4.0（英语：C Sharp 4.0） Cecil（英语：Cecil (programming language)） Clojure Common Lisp（通过Common Lisp对象系统） Dylan Fortress（英语：Fortress (programming language)） Groovy Lasso（英语：Lasso (programming language)） Nim，在v0.20之后泛化方法被废止，使用多方法需要加编译指令 Raku R Seed7（英语：Seed7） TADS（英语：TADS） VB.Net，通过后期绑定，还通过.Net DLR Wolfram语言，通过符号模式匹配 Xtend（英语：Xtend） 任何.NET语言（通过库MultiMethods.NET） C（通过库C Object System） C#（通过库multimethod-sharp） C++（通过库yomm2和multimethods） D（通过库openmethods） Factor（通过标准multimethods词汇表) Java（使用扩展MultiJava） JavaScript（通过包@arrows/multimethod） Perl（通过模块Class::Multimethods） Python（模块multimethod、multipledispatch或Reg库） Racket（通过库multimethod-lib） Ruby（通过Multiple Dispatch库、Multimethod包和Vlx-Multimethods包） Scheme（通过TinyCLOS） TypeScript（通过包@arrows/multimethod） C语言没有动态分派，也可以不使用C Object System库，而以某种形式手工实现。动态分派经常使用enum来标识一个对象的子类型，然后可通过在函数指针分支表（英语：branch table）中查找这个值来完成。C语言模拟多方法的简单例子： typedef void (*CollisionCase)(void); void collision_AA(void) { /* handle Asteroid-Asteroid collision */ }; void collision_AS(void) { /* handle Asteroid-Spaceship collision */ }; void collision_SA(void) { /* handle Spaceship-Asteroid collision */ }; void collision_SS(void) { /* handle Spaceship-Spaceship collision*/ }; typedef enum { THING_ASTEROID = 0, THING_SPACESHIP, THING_COUNT /* not a type of thing itself, instead used to find number of things */ } Thing; CollisionCase collisionCases[THING_COUNT][THING_COUNT] = { {&collision_AA, &collision_AS}, {&collision_SA, &collision_SS} }; void collide(Thing a, Thing b) { (*collisionCases[a][b])(); } int main(void) { collide(THING_SPACESHIP, THING_ASTEROID); } 在只有单一分派的语言比如Java中，多分派可以用多层单一分派来模拟： interface Collideable { void collideWith(final Collideable other); /* These methods would need different names in a language without method overloading. */ void collideWith(final Asteroid asteroid); void collideWith(final Spaceship spaceship); } class Asteroid implements Collideable { public void collideWith(final Collideable other) { // Call collideWith on the other object. other.collideWith(this); } public void collideWith(final Asteroid asteroid) { // Handle Asteroid-Asteroid collision. } public void collideWith(final Spaceship spaceship) { // Handle Asteroid-Spaceship collision. } } class Spaceship implements Collideable { public void collideWith(final Collideable other) { // Call collideWith on the other object. other.collideWith(this); } public void collideWith(final Asteroid asteroid) { // Handle Spaceship-Asteroid collision. } public void collideWith(final Spaceship spaceship) { // Handle Spaceship-Spaceship collision. } } 运行时间instanceof检查可以在一个或两个层次上使用。 TypeScript的多方法示例： import { multi, method, Multi } from '@arrows/multimethod' class Asteroid {} class Spaceship {} type CollideWith = Multi & { (x: Asteroid, y: Asteroid): void (x: Asteroid, y: Spaceship): void (x: Spaceship, y: Asteroid): void (x: Spaceship, y: Spaceship): void } const collideWith: CollideWith = multi( method([Asteroid, Asteroid], (x, y) => { // deal with asteroid hitting asteroid }), method([Asteroid, Spaceship], (x, y) => { // deal with asteroid hitting spaceship }), method([Spaceship, Asteroid], (x, y) => { // deal with spaceship hitting asteroid }), method([Spaceship, Spaceship], (x, y) => { // deal with spaceship hitting spaceship }), ) Python的multimethods.py示例： from multimethods import Dispatch from game_objects import Asteroid, Spaceship from game_behaviors import as_func, ss_func, sa_func collide = Dispatch() collide.add_rule((Asteroid, Spaceship), as_func) collide.add_rule((Spaceship, Spaceship), ss_func) collide.add_rule((Spaceship, Asteroid), sa_func) def aa_func(a, b): """Behavior when asteroid hits asteroid.""" # ...define new behavior... collide.add_rule((Asteroid, Asteroid), aa_func) # ...later... collide(thing1, thing2) Python的van Rossum最初的多方法实现： @multimethod(Asteroid, Asteroid) def collide(a, b): """Behavior when asteroid hits a asteroid.""" # ...define new behavior... @multimethod(Asteroid, Spaceship) def collide(a, b): """Behavior when asteroid hits a spaceship.""" # ...define new behavior... # ... define other multimethod rules ... Ranka, Sanjay; Banerjee, Arunava; Biswas, Kanad Kishore; Dua, Sumeet; Mishra, Prabhat; Moona, Rajat. Contemporary Computing: Second International Conference, IC3 2010, Noida, India, August 9–11, 2010. Proceedings. Springer. 2010-07-26 [2021-03-23]. ISBN 9783642148248. （原始内容存档于2021-04-27）.  Igor Wojda. Programmer dictionary: Receiver. Kt.Academy. 2018-02-08 [2020-02-27] （英语）.  Bezanson, Jeff; Edelman, Alan; Karpinski, Stefan; Shah, Viral B. Julia: A fresh approach to numerical computing. SIAM Review. 7 February 2017, 59 (1): 65–98. arXiv:1411.1607 . doi:10.1137/141000671.  Using type dynamic (C# Programming Guide). [2020-05-14]. （原始内容存档于2021-05-26）.  switch expression (C# reference). [2020-05-14]. （原始内容存档于2021-06-28）.  Basic concepts. [2020-05-14]. （原始内容存档于2021-04-16）.  Dynamic .NET - Understanding the Dynamic Keyword in C# 4. [2020-05-14]. （原始内容存档于2021-05-24）.  Groovy - Multi-methods. [2021-04-15]. （原始内容存档于2021-04-16）.  @arrows/multimethod （页面存档备份，存于互联网档案馆） Multiple dispatch in JavaScript/TypeScript with configurable dispatch resolution by Maciej Cąderek. multimethods.py （页面存档备份，存于互联网档案馆）, Multiple dispatch in Python with configurable dispatch resolution by David Mertz, et al. Five-minute Multimethods in Python. [2014-07-13]. （原始内容存档于2021-05-29）.  multipledispatch. [2021-04-15]. （原始内容存档于2020-11-11）.  Coady, Aric, multimethod: Multiple argument dispatching., [2021-01-28], （原始内容存档于2020-12-31）  PEAK-Rules 0.5a1.dev. Python Package Index. [21 March 2014]. （原始内容存档于2017-03-14）.  PyProtocols. Python Enterprise Application Kit. [26 April 2019]. （原始内容存档于2021-05-05）.  Reg. Read the docs. [26 April 2019]. （原始内容存档于2021-03-05）.  C Object System: A framework that brings C to the level of other high level programming languages and beyond: CObjectSystem/COS. 2019-02-19 [2021-03-30]. （原始内容存档于2021-05-01）.  Methods. The Julia Manual. Julialang. [11 May 2014]. （原始内容存档于2016-07-17）.  Multimethods in C# 4.0 With 'Dynamic'. [2009-08-20]. （原始内容存档于2009-08-25）.  Cecil Language. [2008-04-13]. （原始内容存档于2016-09-01）.  Multimethods in Clojure. [2008-09-04]. （原始内容存档于2015-09-20）.  Steele, Guy L. 28. Common LISP: The Language. Bedford, MA, U.S.A: Digital Press. 1990 [2021-03-30]. ISBN 978-1-55558-041-4. （原始内容存档于2017-12-17）.  Background and Goals. [2008-04-13]. （原始内容存档于2020-04-04）.  The Fortress Language Specification, Version 1.0 (PDF). [2010-04-23]. （原始内容 (PDF)存档于2013-01-20）.  Multimethods in Groovy. [2008-04-13]. （原始内容存档于2011-08-12）.  Methods – LassoGuide 9.2. [2014-11-11]. （原始内容存档于2021-06-13）.  Visitor Pattern Versus Multimethods. [2008-04-13]. （原始内容存档于2021-02-05）.  Nim Manual: Multi-methods. [2020-09-11]. （原始内容存档于2021-06-15）.  Perl 6 FAQ. [2008-04-13]. （原始内容存档于2012-03-13）.  How S4 Methods Work (PDF). [2008-04-13]. （原始内容存档 (PDF)于2021-05-10）.  Multiple Dispatch in Seed7. [2011-04-23]. （原始内容存档于2021-01-29）.  TADS 3 System Manual. [2012-03-19]. （原始内容存档于2017-02-14）.  VB.Net Multiple Dispatch. [2020-03-31]. （原始内容存档于2021-04-11）.  New Features in C#4.0 and VB.Net 10.0. [2020-03-31]. （原始内容存档于2021-02-01）.  Notes for Programming Language Experts. [2016-08-21]. （原始内容存档于2021-06-26）.  Multiple dispatch. [2021-03-30]. （原始内容存档于2021-05-08）.  MultiMethods.NET. [2014-07-13]. （原始内容存档于2010-02-26）.  multimethod-sharp. [2021-03-30]. （原始内容存档于2016-01-23）.  yomm2. [2021-03-30]. （原始内容存档于2020-11-12）.  multimethods. [2021-03-30]. （原始内容存档于2020-11-22）.  openmethods multimethods vocabulary. [2014-07-13]. （原始内容存档于2021-04-29）.  MultiJava. [2014-07-13]. （原始内容存档于2021-04-11）.  Class::Multimethods. [2014-07-13]. （原始内容存档于2013-10-21）.  multimethod-lib. [2021-03-30]. （原始内容存档于2021-04-29）.  The Multiple Dispatch Library. [2021-03-30]. （原始内容存档于2021-04-30）.  Multimethod Package. [2021-03-30]. （原始内容存档于2021-04-29）.  Vlx-Multimethods Package. [2021-03-30]. （原始内容存档于2021-04-27）.  TinyCLOS. [2014-07-13]. （原始内容存档于2008-12-11）.  Stroustrup, Bjarne; Solodkyy, Yuriy; Pirkelbauer, Peter. Open Multi-Methods for C++ (PDF). ACM 6th International Conference on Generative Programming and Component Engineering. 2007 [2014-07-13]. （原始内容存档 (PDF)于2021-04-29）.  Dynamic multiple dispatch. docs.racket-lang.org. [2018-03-12]. （原始内容存档于2021-04-29）.