Többszörös metódusfeloldás


A többszörös metódusfeloldás vagy multimetódusok egyes programozási nyelvek olyan jellemzője, amelyben egy függvény vagy metódus dinamikusan metódusfeloldásra kerülhet a futásidő (dinamikus) típusa vagy általánosabb esetben több argumentumának valamilyen más attribútuma alapján.[1] Ez az egyszeri metódusfeloldású polimorfizmus általánosítása, ahol egy függvény vagy metódus hívása dinamikusan metódusfeloldásra kerül annak az objektumnak a származtatott típusa alapján, amelyen a metódust hívták. A többszörös metódusfeloldás a dinamikus metódusfeloldást a végrehajtó függvényhez vagy metódushoz irányítja egy vagy több argumentum kombinált jellemzőinek felhasználásával.

A metódusfeloldás megértése

A számítógépes szoftverek fejlesztői a forráskódot általában megnevezett blokkokba szervezik, amelyeket különbözőképpen neveznek szubrutinoknak, eljárásoknak, alprogramoknak, függvényeknek vagy módszereknek. A függvényben lévő kódot a függvény meghívásával - a nevére hivatkozó kódrészlet lefuttatásával - hajtják végre. Ez átmenetileg átadja a vezérlést a hívott függvénynek; amikor a függvény végrehajtása befejeződött, a vezérlés jellemzően visszakerül a hívó utasításhoz, amely a hivatkozást követi.

A függvényneveket általában úgy választják ki, hogy azok leírják a függvény feladatát. Néha kívánatos, hogy több függvénynek ugyanazt a nevet adjuk, gyakran azért, mert gyakorlatilag hasonló feladatokat látnak el, de különböző típusú bemeneti adatokkal dolgoznak. Ilyen esetekben a függvényhívás helyén lévő névreferencia nem elegendő a végrehajtandó kódblokk azonosításához. Ehelyett a függvényhívás argumentumainak száma és típusa is felhasználásra kerül a több függvény megvalósítása közötti kiválasztáshoz.

A hagyományosabb, azaz az egyszeres metódusfeloldású objektumorientált programozási nyelvekben egy metódus meghívásakor (Smalltalkban egy üzenet küldése, C++-ban egy tagfüggvény hívása) az egyik argumentumot speciálisan kezelik, és arra használják, hogy meghatározzák, hogy az adott nevű metódusok (potenciálisan sok) osztálya közül melyiket kell alkalmazni. Sok nyelvben a speciális argumentumot szintaktikailag jelzik; például számos programozási nyelv a speciális argumentumot egy pont elé teszi a metódushívás során: special.method(other, arguments, here), így a lion.sound() üvöltést, míg a sparrow.sound() csiripelést eredményezne.

Ezzel szemben a többszörös metódusfeloldást használó nyelvekben a metódus kiválasztása egyszerűen az alapján történik, hogy melyik metódus az, amelynek argumentumai megfelelnek a meghívásakor használt argumentumok számával és típusával. Nem használnak olyan speciális argumentumot a függvényhívás során, amely egy adott függvény/metódus „tulajdonosa” lenne.

A Common Lisp Object System (CLOS) a többszörös metódusfeloldás korai és jól ismert példája.

Adattípusok

Amikor olyan nyelvekkel dolgozunk, amelyek képesek az adattípusok megkülönböztetésére fordítási időben, akkor az alternatívák közötti választás már ekkor megtörténhet. Az ilyen alternatív függvények létrehozását a fordítási idejű kiválasztáshoz általában egy függvény túlterhelésnek nevezik.

Azokban a programozási nyelvekben, amelyek az adattípusok azonosítását a futási időre halasztják (azaz késői kötés), az alternatív függvények közötti választásnak ekkor kell megtörténnie, a függvény argumentumainak dinamikusan meghatározott típusai alapján. Azokat a függvényeket, amelyek alternatív implementációi ilyen módon kerülnek kiválasztásra, legáltalánosabban multimetódusoknak nevezzük.

A függvényhívások dinamikus metódusfeloldásnak van némi futásidő költsége. Egyes nyelvekben a túlterhelés és a multimetódusok függvényhívás közötti különbségtétel elmosódhat, és a fordító határozza meg, hogy a fordítási idejű kiválasztás alkalmazható-e egy adott függvényhívásra, vagy pedig lassabb futási idejű metódusfeloldásra van szükség.

Gyakorlati alkalmazás

Annak becslésére, hogy a gyakorlatban milyen gyakran használják a többszörös metódusfeloldást, Muschevici et al.[2] olyan programokat vizsgált, amelyek dinamikus metódusfeloldást használnak. Kilenc, többnyire fordítóprogramot elemeztek, amelyeket hat különböző nyelven írtak: Ezek a nyelvek a Common Lisp Object System, a Dylan, a Cecil, a MultiJava, a Diesel és a Nice. Eredményeik azt mutatják, hogy a generikus függvények 13-32%-a használja egy argumentum dinamikus típusát, míg 2,7-6,5%-uk több argumentum dinamikus típusát. A generikus függvények fennmaradó 65-93%-ának egy konkrét metódusa van (overrider), és így úgy tekintik, hogy nem használják argumentumaik dinamikus típusát. A tanulmány továbbá arról számol be, hogy a generikus függvények 2-20%-ának két, 3-6%-ának pedig három konkrét függvény implementációja van. A számok gyorsan csökkennek a több konkrét overriderrel rendelkező függvények esetében.

A többszörös metódusfeloldást sokkal erőteljesebben használják a Juliában, ahol a többszörös metódusfeloldást a nyelv eredetétől fogva központi tervezési koncepciónak tekintették: a Muschevici által elemzett, az általános függvényenkénti átlagos metódusszámra vonatkozó statisztikákat összegyűjtve kiderült, hogy a Julia szabványkönyvtárában több mint kétszer annyi túlterhelést használnak, mint a Muschevici által elemzett többi nyelvben, és több mint tízszer annyit a bináris operátorok[3] esetében.

Az ezekből a tanulmányokból származó adatokat a következő táblázatban foglaljuk össze, ahol a DR metódusfeloldási arány (dispatch ratio) az egy általános függvényre jutó módszerek átlagos száma; a CR választási arány (choice ratio) a módszerek számának négyzetének átlaga (a nagyszámú módszerrel rendelkező függvények gyakoriságának jobb mérése érdekében);[2][3] a DoS specializációs fok pedig a típus-specifikált argumentumok átlagos száma metódusonként (azaz a feladott argumentumok száma):

NyelvÁtlag # metódusok (DR)Választási arány

(CR)

Feladott argumentumok száma (DoS)
Cecil[2]2.3363.301.06
Common Lisp (CMU)[2]2.036.341.17
Common Lisp (McCLIM)[2]2.3215.431.17
Common Lisp (Steel Bank)[2]2.3726.571.11
Diesel[2]2.0731.650.71
Dylan (Gwydion)[2]1.7418.272.14
Dylan (OpenDylan)[2]2.5143.841.23
Julia[3]5.8651.441.54
Julia (operators only)28.1378.062.01
MultiJava[2]1.508.921.02
Nice[2]1.363.460.33

Elmélet

A többszörös metódusfeloldású nyelvek elméletét először Castagna et al. dolgozta ki, a késői kötéssel[4][5] rendelkező, túlterhelt függvények modelljének meghatározásával. Ez eredményezte az objektumorientált nyelvek[6] kovariancia és kontravariancia problémájának első formalizálását és a bináris módszerek problémájának megoldását.[7]

Példák

A többszörös és az egyszeri metódusfeloldás megkülönböztetését egy példával lehet világosabbá tenni. Képzeljünk el egy játékot, amelynek (felhasználó által látható) tárgyai, űrhajói és aszteroidái vannak. Két objektum ütközésekor előfordulhat, hogy a programnak különböző dolgokat kell tennie attól függően, hogy mi éppen eltalálta.

Beépített többszörös metódusfeloldással rendelkező nyelvek

C #

A C# a 4. verzióban[8] (2010. április) vezette be a dinamikus többmódusú metódusok támogatását a 'dynamic' kulcsszóval. A következő példa a 8-as verzióban[9] (2019 szeptember) bevezetett switch-kifejezésekkel együtt mutatja be a multimódszereket. Sok más statikusan gépelt nyelvhez hasonlóan a C# is támogatja a statikus metódus-túlterhelést.[10]A Microsoft arra számít, hogy a fejlesztők a legtöbb esetben a statikus gépelést fogják választani a dinamikus gépeléssel szemben.[11] A 'dynamic' kulcsszó támogatja a COM-objektumokkal és a dinamikusan gépelt .NET-nyelvekkel való átjárhatóságot.

class Program    {        static void Main()        {            Console.WriteLine(Utkozteto.osszeutkozes(new Aszteroida(101), new Urhajo(300)));            Console.WriteLine(Utkozteto.osszeutkozes(new Aszteroida(10), new Urhajo(10)));            Console.WriteLine(Utkozteto.osszeutkozes(new Urhajo(101), new Urhajo(10)));        }    }    static class Utkozteto    {        public static string osszeutkozes(UrTargy x, UrTargy y) =>            ((x.Meret > 100) && (y.Meret > 100)) ?                "Nagy bumm!" : CollideWith(x as dynamic, y as dynamic);        private static string CollideWith(Aszteroida x, Aszteroida y) => "a/a";        private static string CollideWith(Aszteroida x, Urhajo y) => "a/s";        private static string CollideWith(Urhajo x, Aszteroida y) => "s/a";        private static string CollideWith(Urhajo x, Urhajo y) => "s/s";    }    abstract class UrTargy    {        public UrTargy(int size) => Meret = size;        public int Meret { get; }    }    class Aszteroida : UrTargy    {        public Aszteroida(int meret) : base(meret) { }    }    class Urhajo : UrTargy    {        public Urhajo(int meret) : base(meret) { }    }

Kimenet:

Nagybumma/ss/s

Groovy

A Groovy egy általános célú, Java kompatibilis/kölcsönösen használható JVM nyelv, amely a Javával ellentétben késői kötést / többszörös diszpatchet használ.[12]

 /*    A fenti C# példa Groovy implementációja    A késői kötés ugyanúgy működik, ha nem statikus metódusokat használunk, vagy ha statikusan fordítjuk az osztályokat/módszereket.    (@CompileStatic annotáció)    */    class Program    {        static void main(String[] args)        {            println Utkozteto.osszeutkozes(new Aszteroida(101), new Urhajo(300))          println Utkozteto.osszeutkozes(new Aszteroida(10), new Urhajo(10))          println Utkozteto.osszeutkozes(new Urhajo(101), new Urhajo(10))        }    }    class Collider    {        static String osszeutkozes(UrTargy x, UrTargy y)        {            (x.meret > 100 && y.meret > 100) ? "Nagy bumm" : collideWith(x, y) // Dinamikus küldés a collideWith módszerhez  }        private static String collideWith(Aszteroida x, Aszteroida y) { "a/a" }        private static String collideWith(Aszteroida x, Urhajo y) { "a/s" }        private static String collideWith(Urhajo x, Aszteroida y) { "s/a" }        private static String collideWith(Urhajo x, Urhajo y) { "s/s"}    }    class UrTargy    {        int meret      UrTargy(int size) { this.meret = size }    }    @InheritConstructors class Aszteroida extends UrTargy { }    @InheritConstructors class Urhajo extends UrTargy { }

Common lisp

Egy többszörös metódusfeloldással rendelkező nyelvben, mint például a Common Lisp, ez inkább így nézhet ki (Common Lisp példa látható):

 (defmethod collide-with((x aszteroida) (y aszteroida))  ;; az aszteroidával való ütközéssel foglalkozik  )(defmethod collide-with((x aszteroida) (y urhajo))  ;; az űrhajóba csapódó aszteroidával foglalkozik  )(defmethod collide-with((x urhajo) (y aszteroida))  ;; aszteroidába csapódó űrhajóval foglalkozik.  )(defmethod collide-with((x urhajo) (y urhajo))  ;; űrhajóval ütköző űrhajóval foglalkozik.  )

és hasonlóan a többi módszer esetében is. Explicit tesztelés és "dinamikus kiosztás" nem használatos.

A többszörös metódusfeloldás mellett a hagyományos elképzelés, miszerint a metódusok osztályokban definiáltak és objektumokban vannak, kevésbé tetszetős - minden egyes fenti, ütközéses metódus két különböző osztályhoz tartozik, nem pedig egyhez. Ezért a metódushívás speciális szintaxisa általában eltűnik, így a metódushívás pontosan úgy néz ki, mint a szokásos függvényhívás, és a metódusok nem osztályokba, hanem általános függvényekbe vannak csoportosítva.

Julia

A Julia beépített többszörös metódusfeloldással rendelkezik, és ez a nyelv tervezésének központi eleme.[3] A fenti példa Julia változataként a következőképpen nézhet ki:

(collide_with(x::Aszteroida, y::Aszteroida) = ... # az aszteroidával való ütközéssel foglalkozik.collide_with(x::Aszteroida, y::Urhajo) = ... # az űrhajóba csapódó aszteroidával foglalkozik.collide_with(x::Urhajo, y::Aszteroida) = ... # aszteroidába csapódó űrhajóval foglalkozik.collide_with(x::Urhajo, y::Urhajo) = ... # űrhajóval ütköző űrhajóval foglalkozik

Next Generation Shell

A Next Generation Shell beépített többszörös metódusfeloldással és predikátummetódusfeloldással rendelkezik, és ezek központi szerepet játszanak a nyelv tervezésében.[13]

Az azonos nevű metódusok többszörös metódusfeloldású metódust alkotnak, ezért nincs szükség külön deklarációra.

Amikor egy többszörös átadás metódus meghívására kerül sor, a jelölt módszereket alulról felfelé haladva keresi a rendszer. Amennyiben az argumentumok típusai megegyeznek a paraméterekhez megadott típusokkal, a metódus meghívásra kerül. Ez ellentétben áll sok más nyelvvel, ahol a legtipikusabb típusú egyezés nyer. Egy meghívott metóduson belül egy sikertelen őrfeltétel (ahol a őrfeltétel hamisnak értékelődik) a meghívandó metódus keresését folytatja.

{type UrTargy    type Aszteroida(UrTargy)    type Spaceship(UrTargy)}    F init(o:UrTargy, meret:Int) o.meret = meret    type Aszteroida(UrTargy)    F osszeutkozes(x:Aszteroida, y:Aszteroida) "a/a"F osszeutkozes(x:Aszteroida, y:Urhajo) "a/s"F osszeutkozes(x:Urhajo, y:Aszteroida) "s/a"F osszeutkozes(x:Urhajo, y:Urhajo) "s/s"F osszeutkozes(x:UrTargy, y:UrTargy){    guard x.meret > 100    guard y.meret > 100    "Nagy bumm"}echo(osszeutkozes(Aszteroida(101), Urhajo(300)))echo(osszeutkozes(Aszteroida(10), Urhajo(10)))    }

Kimenet:

Nagy bumma/s

Raku

A Raku, a Perl-hez hasonlóan, más nyelvek bevált ötleteit használja, és a típusrendszerek megmutatták, hogy meggyőző előnyöket kínálnak a fordítóoldali kódelemzésben és a felhasználóoldali szemantikában a többszörös metódusfeloldás révén.

Mind multimetódusokkal, mind multiszubokkal rendelkezik. Mivel a legtöbb operátor szubrutin, többszörösen feladott operátorokkal is rendelkezik.

A szokásos típuskorlátozások mellett rendelkezik where-korlátozásokkal is, amelyek lehetővé teszik nagyon speciális szubrutinok elkészítését.

Nyelvek bővítése többszörös metódusfeloldású könyvtárakkal

JavaScript

Azokban a nyelvekben, amelyek nem támogatják a többszörös metódusfeloldást a nyelvi definíció vagy a szintaktika szintjén, gyakran lehetséges a többszörös metódusfeloldást könyvtár bővítéssel hozzáadni. A JavaScript és a TypeScript nem támogatja a többszörös metódusokat szintaktikai szinten, de lehetőség van többszörös metódusfeloldás hozzáadására egy könyvtáron keresztül. A multimethod csomag[14]például többszörös metódusfeloldású, általános függvények implementációját biztosítja.

Dinamikusan gépelt változat JavaScriptben:

import { multi, method } from '@arrows/multimethod'class Asteroid {}class Spaceship {}const collideWith = multi(  method([Asteroid, Asteroid], (x, y) => {    // asztreoida asztreoidával való ütközésével foglalkozik  }),  method([Asteroid, Spaceship], (x, y) => {    // asztreoida űrhajóval való ütközésével foglalkozik  }),  method([Spaceship, Asteroid], (x, y) => {    // űrhajó asztreoidával való ütközésével foglalkozik  }),  method([Spaceship, Spaceship], (x, y) => {    // űrhajó űrhajóval való ütközésével foglalkozik  }),)

Statikusan gépelt verzió TypeScriptben:

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) => {    // asztreoida asztreoidával való ütközésével foglalkozik  }),  method([Asteroid, Spaceship], (x, y) => {    // asztreoida űrhajóval való ütközésével foglalkozik  }),  method([Spaceship, Asteroid], (x, y) => {    // űrhajó asztreoidával való ütközésével foglalkozik  }),  method([Spaceship, Spaceship], (x, y) => {    // űrhajó űrhajóval való ütközésével foglalkozik  }),)

Python

A Pythonhoz egy könyvtárbővítmény segítségével többszörös metódusfeloldás adható hozzá. Például a multimethods.py[15] és a multimethods.py[16] modullal is, amely CLOS-stílusú multimódszereket biztosít a Python számára anélkül, hogy megváltoztatná a nyelv alapvető szintaxisát vagy kulcsszavait.

from multimethods import Dispatchfrom game_objects import Asteroid, Spaceshipfrom game_behaviors import as_func, ss_func, sa_funccollide = 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):    """Viselkedés aszteroida aszteroidával való ütközéskor."""    # ...új viselkedésmód meghatározása...collide.add_rule((Asteroid, Asteroid), aa_func)
# ...késöbb...collide(thing1, thing2)

Funkcionálisan ez nagyon hasonló a CLOS példához, de a szintaxis hagyományos Python.Guido van Rossum a Python 2.4 dekorátorok segítségével elkészítette a multimetódusok[17] egyszerűsített szintaxisú mintaimplementációját:

@multimethod(Asteroid, Asteroid)def collide(a, b):    """Viselkedés aszteroida aszteroidával való ütközéskor."""    # ...új viselkedésmód meghatározása...@multimethod(Asteroid, Spaceship)def collide(a, b):    """Viselkedés aszteroida űrhajóval való ütközéskor."""    # ...új viselkedésmód meghatározása...# ... egyéb többmódszeres szabályok meghatározása ...

majd a továbbiakban definiálja a multimetódus dekorátort.

A PEAK-Rules csomag a fenti példához hasonló szintaxissal többszörös metódusfeloldást biztosít.[18] Ezt később a PyProtocols váltotta fel.[19]

A Reg könyvtár támogatja a többszörös- és a predikátum-metódusfeloldást is.[20]

Többszörös metódusfeloldás emulálása

C

A C nem rendelkezik dinamikus metódusfeloldással, ezért ezt valamilyen formában manuálisan kell megvalósítani. Gyakran egy enumot használnak egy objektum altípusának azonosítására. A dinamikus metódusfeloldás úgy végezhető el, hogy ezt az értéket megkeressük egy függvénymutató elágazási táblázatban. Íme egy egyszerű példa C nyelven:

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 /* nem maga a dolog típusa, hanem a dolgok számának megtalálására használják. */} 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);}

A C Object System könyvtárral a C támogatja a CLOS-hoz hasonló dinamikus metódusfeloldást.[21]Teljesen bővíthető, és nincs szükség a metódusok kézi kezelésére. A dinamikus üzeneteket (metódusokat) a COS diszpécser diszpécserével küldi el, ami gyorsabb, mint az Objective-C. Íme egy példa a COS-ban:

#include <stdio.h>#include <cos/Object.h>#include <cos/gen/object.h>// osztályokdefclass (Asteroid)// adattagokendclassdefclass (Spaceship)// adattagokendclass// generikusokdefgeneric (_Bool, collide_with, _1, _2);// multimetódusokdefmethod (_Bool, collide_with, Asteroid, Asteroid) // asztreoida asztreoidával való ütközésével foglalkozikendmethoddefmethod (_Bool, collide_with, Asteroid, Spaceship) // asztreoida űrhajóval való ütközésével foglalkozikendmethoddefmethod (_Bool, collide_with, Spaceship, Asteroid) // űrhajó asztreoidával való ütközésével foglalkozikendmethoddefmethod (_Bool, collide_with, Spaceship, Spaceship) // űrhajó űrhajóval való ütközésével foglalkozikendmethod// használati példaint main(void){  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);}

C ++

2021-től a C++ natívan csak az egyszeri metódusfeloldást támogatja, bár Bjarne Stroustrup (és munkatársai) 2007-ben javasolták a multimetódusok (többszörös metódusfeloldás) hozzáadását.[22] A korlátok megkerülésének módszerei analógok: vagy a visitor minta, vagy a dynamic cast, vagy egy könyvtár használata:

 // Példa a futásidejű típus-összehasonlítás használatára dynamic_cast segítségével struct Thing {     virtual void collideWith(Thing& other) = 0; }; struct Asteroid : Thing {     void collideWith(Thing& other) {         // dynamic_cast egy pointer típusra NULL-t ad vissza, ha az átvitel sikertelen.         // (a dinamikus_cast referenciatípusra való átvitel hiba esetén kivételt dobna)         if (auto asteroid = dynamic_cast<Asteroid*>(&other)) {             // Aszteroida-aszteroida ütközés kezelése         } else if (auto spaceship = dynamic_cast<Spaceship*>(&other)) {             // Aszteroida-űrhajó ütközés kezelése         } else {             // alapértelmezett ütközéskezelés itt         }     } }; struct Spaceship : Thing {     void collideWith(Thing& other) {         if (auto asteroid = dynamic_cast<Asteroid*>(&other)) {             // kezeli az űrhajó-aszteroida ütközést         } else if (auto spaceship = dynamic_cast<Spaceship*>(&other)) {             // kezeli az űrhajó-űrhajó ütközést         } else {             // alapértelmezett ütközéskezelés itt         }     } };

vagy pointer-to-method keresési táblázat:

#include <cstdint>#include <typeinfo>#include <unordered_map>class Thing {  protected:    Thing(std::uint32_t cid) : tid(cid) {}    const std::uint32_t tid; // type id    typedef void (Thing::*CollisionHandler)(Thing& other);    typedef std::unordered_map<std::uint64_t, CollisionHandler> CollisionHandlerMap;    static void addHandler(std::uint32_t id1, std::uint32_t id2, CollisionHandler handler) {        collisionCases.insert(CollisionHandlerMap::value_type(key(id1, id2), handler));    }    static std::uint64_t key(std::uint32_t id1, std::uint32_t id2) {        return std::uint64_t(id1) << 32 | id2;    }    static CollisionHandlerMap collisionCases;  public:    void collideWith(Thing& other) {        auto handler = collisionCases.find(key(tid, other.tid));        if (handler != collisionCases.end()) {            (this->*handler->second)(other); // pointer-to-method call        } else {            // alapértelmezett ütközéskezelés itt        }    }};class Asteroid: public Thing {    void asteroid_collision(Thing& other)   { /*Aszteroida-aszteroida ütközés kezelése*/ }    void spaceship_collision(Thing& other)  { /*Aszteroida-űrhajó ütközés kezelése*/}  public:    Asteroid(): Thing(cid) {}    static void initCases();    static const std::uint32_t cid;};class Spaceship: public Thing {    void asteroid_collision(Thing& other)   { /*űrhajó-aszteroida ütközés kezelése*/}    void spaceship_collision(Thing& other)  { /*űrhajó-űrhajó ütközés kezelése*/}  public:    Spaceship(): Thing(cid) {}    static void initCases();    static const std::uint32_t cid; // class id};Thing::CollisionHandlerMap Thing::collisionCases;const std::uint32_t Asteroid::cid = typeid(Asteroid).hash_code();const std::uint32_t Spaceship::cid = typeid(Spaceship).hash_code();void Asteroid::initCases() {    addHandler(cid, cid, CollisionHandler(&Asteroid::asteroid_collision));    addHandler(cid, Spaceship::cid, CollisionHandler(&Asteroid::spaceship_collision));}void Spaceship::initCases() {    addHandler(cid, Asteroid::cid, CollisionHandler(&Spaceship::asteroid_collision));    addHandler(cid, cid, CollisionHandler(&Spaceship::spaceship_collision));}int main() {    Asteroid::initCases();    Spaceship::initCases();    Asteroid  a1, a2;    Spaceship s1, s2;    a1.collideWith(a2);    a1.collideWith(s1);    s1.collideWith(s2);    s1.collideWith(a1);}

A yomm2 könyvtár[23] a nyílt multimódszerek gyors, ortogonális megvalósítását biztosítja.

A nyílt metódusok deklarálásának szintaxisát egy natív C++ implementációra vonatkozó javaslat ihlette. A könyvtár megköveteli, hogy a felhasználó regisztrálja az összes virtuális argumentumként használt osztályt (és azok alosztályait), de nem igényel semmilyen módosítást a meglévő kódban. A metódusok közönséges inline C++ függvényekként vannak implementálva; túlterhelhetők és átadhatók mutatóval. A virtuális argumentumok száma nincs korlátozva, és tetszőlegesen keverhetők nem virtuális argumentumokkal.

A könyvtár többféle technika kombinációját használja (tömörített elosztótáblák, tökéletes integer hash), hogy a metódushívásokat állandó idő alatt valósítsa meg, miközben csökkenti a memóriahasználatot. Egyetlen virtuális argumentummal rendelkező nyitott metódus hívásának elküldése csak 15-30%-kal több időt vesz igénybe, mint egy közönséges virtuális tagfüggvény hívása, ha egy modern optimalizáló fordítót használunk.

Az Aszteroidák példája a következőképpen valósítható meg:

#include <yorel/yomm2/cute.hpp>using yorel::yomm2::virtual_;class Thing {  public:    virtual ~Thing() {}    // ...};class Asteroid : public Thing {    // ...};class Spaceship : public Thing {    // ...};register_class(Thing);register_class(Spaceship, Thing);register_class(Asteroid, Thing);declare_method(void, collideWith, (virtual_<Thing&>, virtual_<Thing&>));define_method(void, collideWith, (Thing& left, Thing& right)) {    // alapértelmezett ütközéskezelés itt}define_method(void, collideWith, (Asteroid& left, Asteroid& right)) {    // Aszteroida-aszteroida ütközés kezelése}define_method(void, collideWith, (Asteroid& left, Spaceship& right)) {    // Aszteroida-űrhajó ütközés kezelése}define_method(void, collideWith, (Spaceship& left, Asteroid& right)) {    // űrhajó-aszteroida ütközés kezelése}define_method(void, collideWith, (Spaceship& left, Spaceship& right)) {    // űrhajó-űrhajó ütközés kezelése}int main() {    yorel::yomm2::update_methods();    Asteroid  a1, a2;    Spaceship s1, s2;    collideWith(a1, a2);    collideWith(a1, s1);    collideWith(s1, s2);    collideWith(s1, a1);}

Stroustrup megemlíti a The Design and Evolution of C++ című könyvében, hogy tetszett neki a multimetódusok koncepciója, és fontolgatta annak C++-ban való megvalósítását, de azt állítja, hogy nem tudott hatékony mintaimplementációt találni (a virtuális függvényekhez hasonlóan), és megoldani néhány lehetséges típus-többértelműségi problémát. Ezután azt állítja, hogy bár a funkciót továbbra is jó lenne megvalósítani, ez megközelítőleg megvalósítható a fenti C/C++ példában vázolt dupla metódusfeloldással vagy típusalapú keresőtáblával, így a jövőbeli nyelvi revíziókban alacsony prioritású funkciónak számít.[24]

D

2021-től a D - sok más objektumorientált programozási nyelvhez hasonlóan - natívan csak egyszeri metódusfeloldást támogat. Lehetőség van azonban arra, hogy a D-ben könyvtári függvényként emuláljuk a nyitott multimetódusokat.[25]

// DeklarációMatrix plus(virtual!Matrix, virtual!Matrix);// A két DenseMatrix objektum felülírása@methodMatrix _plus(DenseMatrix a, DenseMatrix b){  const int nr = a.rows;  const int nc = a.cols;  assert(a.nr == b.nr);  assert(a.nc == b.nc);  auto result = new DenseMatrix;  result.nr = nr;  result.nc = nc;  result.elems.length = a.elems.length;  result.elems[] = a.elems[] + b.elems[];  return result;}// A két DiagonalMatrix objektum felülírása@methodMatrix _plus(DiagonalMatrix a, DiagonalMatrix b){  assert(a.rows == b.rows);  double[] sum;  sum.length = a.elems.length;  sum[] = a.elems[] + b.elems[];  return new DiagonalMatrix(sum);}

Java

Egy olyan nyelvben, ahol csak egyszeres metódusfeloldás van, mint például a Java, a többszörös metódusfeloldás az egyszeres metódusfeloldás több szintjével emulálható:

interface Collideable {    void collideWith(final Collideable other);    /* Ezeknek a metódusoknak más nevekre lenne szükségük egy olyan nyelvben, ahol nincs metódus-túlterhelés. */    void collideWith(final Asteroid asteroid);    void collideWith(final Spaceship spaceship);}class Asteroid implements Collideable {    public void collideWith(final Collideable other) {        // Hívja a collideWith-t a másik objektummal        other.collideWith(this);   }    public void collideWith(final Asteroid asteroid) {        // Aszteroida-aszteroida ütközés kezelése    }    public void collideWith(final Spaceship spaceship) {        // Aszteroida-űrhajó ütközés kezelése    }}class Spaceship implements Collideable {    public void collideWith(final Collideable other) {        // Hívja a collideWith-t a másik objektummal        other.collideWith(this);    }    public void collideWith(final Asteroid asteroid) {        // űrhajó-aszteroida ütközés kezelése    }    public void collideWith(final Spaceship spaceship) {        // űrhajó-űrhajó ütközés kezelése    }}

A futásidejű instanceof ellenőrzések az egyik vagy mindkét szinten is használhatók.

Programozási nyelvek támogatása

Elsődleges paradigma

Bővítmények

Hivatkozások

Fordítás

Ez a szócikk részben vagy egészben a Multiple dispatch című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.

További információk