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

szerkesztés

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

szerkesztés

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

szerkeszté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.33 63.30 1.06
Common Lisp (CMU)[2] 2.03 6.34 1.17
Common Lisp (McCLIM)[2] 2.32 15.43 1.17
Common Lisp (Steel Bank)[2] 2.37 26.57 1.11
Diesel[2] 2.07 31.65 0.71
Dylan (Gwydion)[2] 1.74 18.27 2.14
Dylan (OpenDylan)[2] 2.51 43.84 1.23
Julia[3] 5.86 51.44 1.54
Julia (operators only) 28.13 78.06 2.01
MultiJava[2] 1.50 8.92 1.02
Nice[2] 1.36 3.46 0.33

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]

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

szerkesztés

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:

Nagybumm
a/s
s/s

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

szerkesztés

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.

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

szerkesztés

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 bumm
a/s

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

szerkesztés

JavaScript

szerkesztés

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
  }),
)

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 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):
    """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

szerkesztés

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ályok

defclass (Asteroid)
// adattagok
endclass

defclass (Spaceship)
// adattagok
endclass

// generikusok

defgeneric (_Bool, collide_with, _1, _2);

// multimetódusok

defmethod (_Bool, collide_with, Asteroid, Asteroid)
 // asztreoida asztreoidával való ütközésével foglalkozik
endmethod

defmethod (_Bool, collide_with, Asteroid, Spaceship)
 // asztreoida űrhajóval való ütközésével foglalkozik
endmethod

defmethod (_Bool, collide_with, Spaceship, Asteroid)
 // űrhajó asztreoidával való ütközésével foglalkozik
endmethod

defmethod (_Bool, collide_with, Spaceship, Spaceship)
 // űrhajó űrhajóval való ütközésével foglalkozik
endmethod

// használati példa

int 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);
}

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]

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
@method
Matrix _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
@method
Matrix _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);
}

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

szerkesztés

Elsődleges paradigma

szerkesztés

Bővítmények

szerkesztés

Hivatkozások

szerkesztés
  1. Contemporary Computing: Second International Conference, IC3 2010, Noida, India, August 9–11, 2010. Proceedings. Springer (2010. július 26.). ISBN 9783642148248 
  2. a b c d e f g h i j k Multiple dispatch in practice, OOPSLA '08. Nashville, TN, USA: ACM, 563–582. o.. DOI: 10.1145/1449764.1449808 (2008). ISBN 9781605582153 
  3. a b c d Bezanson (2017. február 7.). „Julia: A fresh approach to numerical computing”. SIAM Review 59 (1), 65–98. o. DOI:10.1137/141000671.  
  4. (1995) „A calculus for overloaded functions with subtyping”. Information and Computation 117 (1), 115–135. o. DOI:10.1006/inco.1995.1033. (Hozzáférés: 2013. április 19.)  
  5. Castagna, Giuseppe. Object-Oriented Programming: A Unified Foundation, Progress in Theoretical Computer Science. Birkhäuser, 384. o. (1996). ISBN 978-0-8176-3905-1 
  6. Castagna, Giuseppe (1995). „Covariance and contravariance: conflict without a cause”. ACM Transactions on Programming Languages and Systems 17 (3), 431–447. o. DOI:10.1145/203095.203096.  
  7. Bruce (1995). „On binary methods”. Theory and Practice of Object Systems 1 (3), 221–242. o. DOI:10.1002/j.1096-9942.1995.tb00019.x. (Hozzáférés: 2013. április 19.)  
  8. Groovy - Multi-methods
  9. NGSLANG(1) NGS User Manual. ngs-lang.org. (Hozzáférés: 2019. október 1.)
  10. @arrows/multimethod Multiple dispatch in JavaScript/TypeScript with configurable dispatch resolution by Maciej Cąderek.
  11. multimethods.py Archiválva 2005. március 9-i dátummal a Wayback Machine-ben., Multiple dispatch in Python with configurable dispatch resolution by David Mertz, et al.
  12. Five-minute Multimethods in Python
  13. PEAK-Rules 0.5a1.dev. Python Package Index. (Hozzáférés: 2014. március 21.)
  14. PyProtocols. Python Enterprise Application Kit. (Hozzáférés: 2019. április 26.)
  15. Reg. Read the docs. (Hozzáférés: 2019. április 26.)
  16. Report on language support for Multi-Methods and Open-Methods for C ++, 2007. március 11. „Multiple dispatch – the selection of a function to be invoked based on the dynamic type of two or more arguments – is a solution to several classical problems in object-oriented programming.”
  17. yomm2, Fast, Orthogonal Open Multi-Methods for C++ by Jean-Louis Leroy.
  18. Stroustrup, Bjarne. Section 13.8, The Design and Evolution of C++. Indianapolis, IN, U.S.A: Addison Wesley (1994). ISBN 978-0-201-54330-8 
  19. C Object System: A framework that brings C to the level of other high level programming languages and beyond: CObjectSystem/COS, 2019. február 19.
  20. a b Methods. The Julia Manual. Julialang. [2016. július 17-i dátummal az eredetiből archiválva]. (Hozzáférés: 2014. május 11.)
  21. openmethods, Open Multi-Methods for D by Jean-Louis Leroy.
  22. Multimethods in C# 4.0 With 'Dynamic'. (Hozzáférés: 2009. augusztus 20.)
  23. Cecil Language. (Hozzáférés: 2008. április 13.)
  24. Multimethods in Clojure. (Hozzáférés: 2008. szeptember 4.)

Fordítás

szerkeszté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

szerkesztés