Többszörös metódusfeloldás
Ezt a szócikket át kellene olvasni, ellenőrizni a szöveg helyesírását és nyelvhelyességét, a tulajdonnevek átírását. Esetleges további megjegyzések a vitalapon. |
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]C #
[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.
Julia
[szerkesztés]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
Raku
[szerkeszté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]C
[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);
}
C ++
[szerkeszté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]
D
[szerkesztés]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);
}
Java
[szerkesztés]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]- Julia[21]
Bővítmények
[szerkesztés]- Any .NET language (via the library MultiMethods.NET)
- C (via the library C Object System)
- C# (via the library multimethod-sharp)
- C++ (via the library yomm2 and multimethods)
- D (via the library openmethods)
- Factor (via the standard multimethods vocabulary)
- Java (using the extension MultiJava)
- JavaScript (via package @arrows/multimethod)
- Perl (via the module Class::Multimethods)
- Python (via PEAK-Rules, RuleDispatch, gnosis.magic.multimethods, PyMultimethods, or multipledispatch)
- Racket (via multimethod-lib)
- Ruby (via the library The Multiple Dispatch Library and Multimethod Package and Vlx-Multimethods Package)
- Scheme (via e.g. TinyCLOS)
- TypeScript (via package @arrows/multimethod)
Hivatkozások
[szerkesztés]- ↑ Contemporary Computing: Second International Conference, IC3 2010, Noida, India, August 9–11, 2010. Proceedings. Springer (2010. július 26.). ISBN 9783642148248
- ↑ 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
- ↑ 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.
- ↑ (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.)
- ↑ Castagna, Giuseppe. Object-Oriented Programming: A Unified Foundation, Progress in Theoretical Computer Science. Birkhäuser, 384. o. (1996). ISBN 978-0-8176-3905-1
- ↑ 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.
- ↑ 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.)
- ↑ Groovy - Multi-methods
- ↑ NGSLANG(1) NGS User Manual. ngs-lang.org. (Hozzáférés: 2019. október 1.)
- ↑ @arrows/multimethod Multiple dispatch in JavaScript/TypeScript with configurable dispatch resolution by Maciej Cąderek.
- ↑
- ↑ 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.
- ↑ Five-minute Multimethods in Python
- ↑ PEAK-Rules 0.5a1.dev. Python Package Index. (Hozzáférés: 2014. március 21.)
- ↑ PyProtocols. Python Enterprise Application Kit. (Hozzáférés: 2019. április 26.)
- ↑ Reg. Read the docs. (Hozzáférés: 2019. április 26.)
- ↑ 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.”
- ↑ yomm2, Fast, Orthogonal Open Multi-Methods for C++ by Jean-Louis Leroy.
- ↑ 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
- ↑ 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.
- ↑ 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.)
- ↑ openmethods, Open Multi-Methods for D by Jean-Louis Leroy.
- ↑ Multimethods in C# 4.0 With 'Dynamic'. (Hozzáférés: 2009. augusztus 20.)
- ↑ Cecil Language. (Hozzáférés: 2008. április 13.)
- ↑ 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]- (2007) „Open Multi-Methods for C++”. ACM 6th International Conference on Generative Programming and Component Engineering.
- Dynamic multiple dispatch. docs.racket-lang.org. (Hozzáférés: 2018. március 12.)