Ugrás a tartalomhoz

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

Ellenőrzött
A Wikipédiából, a szabad enciklopédiából


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

Elmélet

[szerkesztés]

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

[szerkesztés]

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

Groovy

[szerkeszté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
  }),
)

Python

[szerkesztés]

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]