Multimethod ( engelsk multimethod ) eller multiple dispatch ( engelska multiple dispatch ) är en mekanism i programmeringsspråk som låter dig välja en av flera funktioner beroende på dynamiska typer eller argumentvärden (till exempel metodöverbelastning i vissa programmeringsspråk) . Det är en förlängning av single dispatch ( virtuella funktioner ) där metodval görs dynamiskt baserat på den faktiska typen av objekt som metoden anropades på. Multipelutskick generaliserar dynamisk utskick för ärenden med två eller flera objekt.
Multimetoder stöds uttryckligen av "Common Lisp Object System" ( CLOS ).
Programutvecklare tenderar att gruppera källkoden i namngivna block som kallas anrop, procedurer, subrutiner , funktioner eller metoder. Koden för en funktion exekveras genom att anropa den, vilket består i att exekvera koden som anges med dess namn. I detta fall överförs kontrollen tillfälligt till den anropade funktionen; när denna funktion är klar överförs kontrollen vanligtvis tillbaka till instruktionen efter funktionsanropet.
Funktionsnamn väljs vanligtvis för att beskriva deras syfte. Ibland är det nödvändigt att namnge flera funktioner med samma namn, vanligtvis för att de utför konceptuellt liknande uppgifter, men arbetar med olika typer av indata. I sådana fall räcker inte namnet på funktionen på platsen för dess anrop för att avgöra vilket kodblock som ska anropas. Förutom namnet, i det här fallet, används antalet och typen av argument för den anropade funktionen också för att välja en specifik implementering av funktionen.
I mer traditionella ensändningsobjektorienterade programmeringsspråk, när en metod anropas (sända ett meddelande i Smalltalk , anropa en medlemsfunktion i C++ ), behandlas ett av dess argument på ett speciellt sätt och används för att bestämma vilket av ( potentiellt många) metoder med det namnet måste anropas. På många språk indikeras detta speciella argument syntaktisk, till exempel i ett antal programmeringsspråk placeras ett speciellt argument före punkten när en metod anropas:
speciell metod (annat, argument, här)så lion.sound() kommer att producera ett vrål och sparrow.sound() kommer att producera ett pip.
I motsats till språk med flera utskick är den valda metoden helt enkelt metoden vars argument matchar antalet och typen av argument i funktionsanropet. Det finns inget speciellt argument här som "äger" funktionen eller metoden som refereras till av ett visst anrop.
Common Lisp Object System (CLOS) är en av de första och välkända implementeringarna av multipelutskick.
När man arbetar med språk där datatyper särskiljs vid kompilering, kan val mellan tillgängliga funktionsalternativ ske vid kompilering. Att skapa sådana alternativa funktionsalternativ att välja mellan vid kompilering kallas ofta för funktionsöverbelastning .
I programmeringsspråk som bestämmer datatyper vid körning (sen bindning), måste val bland funktionsalternativ ske vid körning baserat på dynamiskt bestämda funktionsargumenttyper. Funktioner vars alternativa implementeringar väljs på detta sätt kallas vanligtvis för multimetoder.
Det finns vissa körtidskostnader förknippade med att dynamiskt skicka funktionsanrop. På vissa språk kan distinktionen mellan funktionsöverbelastning och multimetoder vara suddig, med kompilatorn som avgör om valet av den anropade funktionen kan göras vid kompilering, eller om det krävs långsammare sändning vid körning.
För att bedöma hur ofta multipel utsändning används i praktiken undersökte Muschevici et al . [1] applikationer som använder dynamisk utsändning. De analyserade nio applikationer, mestadels kompilatorer, skrivna på sex olika programmeringsspråk: Common Lisp Object System , Dylan , Cecil, MultiJava, Diesel och Nice. Resultaten visar att 13 % till 32 % av generiska funktioner använder dynamisk typning med ett argument, medan 2,7 % till 6,5 % av funktionerna använder dynamisk typning med flera argument. De återstående 65%-93% av generiska funktioner har en specifik metod (överbelastad), och ansågs därför inte använda dynamisk typning av sina argument. Dessutom rapporterar studien att mellan 2% och 20% av generiska funktioner hade två och 3%-6% hade tre av sina specifika implementeringar. Andelen funktioner med ett stort antal konkreta implementeringar minskade snabbt.
Teorin om flersamtalsspråk utvecklades först av Castagna et al genom att definiera en modell för överbelastade funktioner med sen bindning [2] [3] . Detta gav den första formaliseringen av problemet med kovarians och motvariation av objektorienterade programmeringsspråk [4] och lösningen av problemet med binära metoder [5] .
För att bättre förstå skillnaden mellan multimetoder och enstaka försändelser kan följande exempel demonstreras. Föreställ dig ett spel där det, tillsammans med en mängd andra föremål, finns asteroider och rymdskepp. När två objekt kolliderar måste programmet välja en viss algoritm av åtgärder, beroende på vad som kolliderade med vad.
I ett flermetodsspråk som Common Lisp skulle koden se ut så här:
( defgenerisk kollidera ( x y )) ( defmetod kolliderar (( x asteroid ) ( y asteroid )) ;; asteroid kolliderar med asteroid ) ( defmetod kolliderar (( x asteroid ) ( y rymdskepp )) ;; asteroid kolliderar med rymdskepp ) ( defmetod kolliderar (( x rymdskepp ) ( y asteroid )) ;; rymdskepp kolliderar med en asteroid ) ( defmethod collide (( x rymdskepp ) ( y rymdskepp )) ;;rymdskepp kolliderar med rymdskepp )och liknande för andra metoder. Explicit kontroll och "dynamisk gjutning" används inte här.
Med multipel sändning blir det traditionella tillvägagångssättet att definiera metoder i klasser och lagra dem i objekt mindre attraktivt, eftersom varje kollidera-med-metod refererar till två olika klasser istället för en. Således försvinner i allmänhet den speciella syntaxen för att anropa en metod, så att ett metodanrop ser ut exakt som ett vanligt funktionsanrop, och metoder grupperas inte efter klass, utan i generiska funktioner .
Raku, som tidigare versioner, använder beprövade idéer från andra språk och typsystem för att erbjuda övertygande fördelar i kodanalys på kompilatorsidan och kraftfull semantik genom flera utskick.
Den har både multimetoder och multisubrutiner. Eftersom de flesta uttalanden är subrutiner, finns det också uttalanden med flera utskick.
Tillsammans med de vanliga typbegränsningarna har den också "var"-typbegränsningar, vilket gör att du kan skapa mycket specialiserade subrutiner.
delmängd Massa av Real där 0 ^..^ Inf ; role Stellar-Object { har massa $ .mass krävs ; metodnamn () returnerar Str { ...}; } klass Asteroid gör Stellar-Object { metodnamn ( ) { 'en asteroid' } } class Spaceship does Stellar-Object { has Str $.name = 'något namnlöst rymdskepp' ; } min Str @destroyed = < utplånad förstörd mangled >; min Str @damaged = " skadad 'krockade med' 'skadades av' "; # Vi lägger till flera kandidater till de numeriska jämförelseoperatorerna eftersom vi jämför dem numeriskt, # men det är inte meningsfullt att låta objekten tvingas till en numerisk typ. # (Om de gjorde tvång skulle vi inte nödvändigtvis behöva lägga till dessa operatorer.) # Vi kunde också ha definierat helt nya operatorer på samma sätt. multi sub infix: " <=> " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . massa <=> $b . mass } multi sub infix: " < " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . massa < $b . mass } multi sub infix: " > " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . massa > $b . mass } multi sub infix: " == " ( Stellar-Object:D $a , Stellar-Object:D $b ) { $a . massa == $b . massa } # Definiera en ny multidispatcher och lägg till några typbegränsningar till parametrarna. # Om vi inte hade definierat det skulle vi ha fått en generisk som inte hade begränsningar. protosub kolliderar ( Stellar -Object:D $, Stellar-Object:D $ ) {*} # Du behöver inte upprepa typerna här eftersom de är samma som prototypen. # "Var"-begränsningen gäller tekniskt sett bara för $b, inte hela signaturen. # Observera att "where"-begränsningen använder "<" operatorkandidaten som vi lade till tidigare. multi sub collide ( $a , $b där $a < $b ) { säg "$a.name() var @destroyed.pick() av $b.name()" ; } multi sub collide ( $a , $b där $a > $b ) { # skicka tillbaka till den tidigare kandidaten med argumenten utbytta samma med $b , $a ; } # Detta måste vara efter de två första eftersom de andra # har "var"-begränsningar, som kontrolleras i #-ordningen som subs skrevs. (Denna skulle alltid matcha. ) multi sub collide ( $a , $b ){ # randomisera ordningen my ( $n1 , $n2 ) = ( $a . name , $b . name ). plocka (*); säg "$n1 @damaged.pick() $n2" ; } # Följande två kandidater kan vara var som helst efter protot, # eftersom de har mer specialiserade typer än de tre föregående. # Om fartygen har ojämn massa blir en av de två första kandidaterna kallad istället. multi sub collide ( Rymdskepp $a , Rymdskepp $b där $a == $b ){ my ( $n1 , $n2 ) = ( $a . namn , $b . namn ). plocka (*); säg "$n1 kolliderade med $n2, och båda fartygen var " , ( @destroyed . pick , 'vänster skadad' ). plocka ; } # Du kan packa upp attributen i variabler i signaturen. # Du kan till och med ha en begränsning för dem `(:mass($a) där 10)`. multi sub collide ( Asteroid $ (: mass ( $a )), Asteroid $ (: mass ( $b )) ){ säg "två asteroider kolliderade och kombinerades till en större asteroid med massan { $a + $b }" ; } mitt rymdskepp $Enterprise .= nytt (: massa ( 1 ),: namn ( 'The Enterprise' )); kollidera Asteroid . new (: massa ( .1 )), $Enterprise ; kollidera $Enterprise , Spaceship . ny (: massa ( .1 )); kollidera $Enterprise , Asteroid . ny (: massa ( 1 )); kollidera $Enterprise , Spaceship . ny (: massa ( 1 )); kollidera Asteroid . ny (: massa ( 10 )), Asteroid . ny (: massa ( 5 ));I språk som inte stöder flera utskick på syntaxnivå, som Python , är det i allmänhet möjligt att använda flera utskick med hjälp av tilläggsbibliotek. Till exempel implementerar modulen multimethods.py [6] multimetoder i CLOS-stil i Python utan att ändra syntax eller språknyckelord.
från multimethods import Skicka från game_objects import Asteroid , rymdskepp från game_behaviors import ASFunc , SSFunc , SAFunc collide = Dispatch () kolliderar . add_rule (( Asteroid , Rymdskepp ), ASFunc ) kolliderar . add_rule (( Rymdskepp , Rymdskepp ), SSFunc ) kolliderar . add_rule (( Rymdskepp , Asteroid ), SAFunc ) def AAFunc ( a , b ): """Beteende när asteroid träffar asteroid""" # ...definiera nytt beteende... kollidera . add_rule (( Asteroid , Asteroid ), AAFunc ) # ...senare... kollidera ( sak1 , sak2 )Funktionellt är detta väldigt likt CLOS-exemplet, men syntaxen följer den vanliga Python-syntaxen.
Genom att använda Python 2.4-dekoratörer skrev Guido van Rossum ett exempel på implementering av multimetoder [7] med en förenklad syntax:
@multimethod ( Asteroid , Asteroid ) def collide ( a , b ): """Beteende när asteroiden träffar asteroiden""" # ...definiera nytt beteende... @multimethod ( Asteroid , Spaceship ) def collide ( a , b ) : """Beteende när asteroid träffar rymdskepp""" # ...definiera nytt beteende... # ...definiera andra multimetodregler ...och sedan definieras dekoratörens multimetod.
PEAK-Rules-paketet implementerar multipel sändning med en syntax som liknar exemplet ovan. [åtta]
På språk som bara har enstaka utskick, som Java , skulle den här koden se ut så här (dock kan besöksmönstret hjälpa till att lösa det här problemet):
/* Exempel med jämförelse av körtidstyp via Javas "instanceof"-operator */ gränssnitt Collideable { /* Att göra detta till en klass skulle inte ändra demonstrationen. */ void collideWith ( Collideable other ); } class Asteroid implements Collideable { public void collideWith ( Collideable other ) { if ( other instansof Asteroid ) { // Hantera Asteroid-Asteroid collision. } else if ( annan instans av rymdskepp ) { // Hantera kollision mellan asteroid och rymdskepp. } } } klass Rymdskepp implementerar Collideable { public void collideWith ( Collideable other ) { if ( other instansof Asteroid ) { // Hantera rymdskepp-asteroidkollision. } else if ( annan instans av rymdskepp ) { // Hantera rymdskepp-rymdskepp kollision. } } }C har ingen dynamisk utsändning, så den måste implementeras för hand i en eller annan form. En uppräkning används ofta för att identifiera en undertyp av ett objekt. Dynamisk sändning kan implementeras genom att slå upp detta värde i grentabellen med funktionspekare. Här är ett enkelt exempel, i C:
typedef void ( * CollisionCase )(); void collision_AA () { /* Asteroid-Asteroid kollision hantering */ }; void collision_AS () { /* Asteroid-Ship kollision hantering */ }; void collision_SA () { /* Ship-Asteroid collision handling */ }; void collision_SS () { /* hantering av kollision från fartyg till fartyg */ }; typedef enum { asteroid = 0 _ rymdskepp , num_thing_types /* är inte en objekttyp, används för att hitta antalet objekt */ } Sak ; CollisionCase collisionCases [ num_thing_types ][ num_thing_types ] = { { & collision_AA , & collision_AS }, { & collision_SA , & collision_SS } }; void kollidera ( sak a , sak b ) { ( * collisionCases [ a ][ b ])(); } int main () { kollidera ( rymdskepp , asteroid ); }Från och med 2015 stöder C++ endast enstaka sändningar, även om stöd för flera sändningar övervägs. [9] Lösningarna för denna begränsning är liknande: antingen genom att använda besöksmönstret eller dynamisk casting:
// Exempel med jämförelse av körtidstyp via dynamic_cast struct Thing { virtual void collideWith ( Thing & other ) = 0 ; }; struct Asteroid : Thing { void kolliderar med ( ting och annat ) { // dynamic_cast till en pekartyp returnerar NULL om casten misslyckas // (dynamic_cast till en referenstyp skulle ge ett undantag vid misslyckande) if ( Asteroid * asteroid = dynamic_cast < Asteroid *> ( & other )) { // hantera asteroid-asteroidkollision } else if ( Rymdskepp * rymdskepp = dynamic_cast < Rymdskepp *> ( & annat )) { // hantera asteroid-rymdskeppskollision } else { // standard kollisionshantering här } } }; struct Spaceship : Thing { void kolliderar med ( ting och annat ) { if ( Asteroid * asteroid = dynamic_cast < Asteroid *> ( & other )) { // hantera rymdskepp-asteroidkollision } else if ( Rymdskepp * rymdskepp = dynamic_cast < Rymdskepp *> ( & annat )) { // hantera rymdskepp-rymdskepp kollision } annat { // standard kollisionshantering här } } };eller uppslagstabeller för pekare till metoder:
#inkludera <typinfo> #inkludera <unordered_map> typedef osignerad uint4 ; typedef osignerad lång lång uint8 ; klass sak { skyddad : Sak ( const uint4 cid ) : tid ( cid ) {} const uint4 tid ; // typ id typedef void ( Thing ::* CollisionHandler )( Thing & other ); typedef std :: unordered_map < uint8 , CollisionHandler > CollisionHandlerMap ; static void addHandler ( const uint4 id1 , const uint4 id2 , const CollisionHandler handler ) { kollisionsfall . infoga ( CollisionHandlerMap :: värde_typ ( nyckel ( id1 , id2 ), hanterare )); } statisk uint8 nyckel ( const uint4 id1 , const uint4 id2 ) { returnera uint8 ( id1 ) << 32 | id2 ; } static CollisionHandlerMap collisionCases ; offentliga : void kolliderar med ( ting och annat ) { CollisionHandlerMap :: const_iterator- hanterare = collisionCases . hitta ( nyckel ( tid , annat . tid )); if ( hanterare != collisionCases . end ()) { ( denna ->* hanterare -> andra )( annan ); // pointer-to-method call } else { // standard kollisionshantering } } }; klass Asteroid : public Thing { void asteroid_collision ( Thing & other ) { /*hantera Asteroid-Asteroid collision*/ } void spaceship_collision ( Thing & other ) { /*hantera Asteroid-Spaceship Collision*/ } offentliga : Asteroid () : Sak ( cid ) {} statisk void initCases (); statisk konst uint4 cid ; }; klass rymdskepp : public Thing { void asteroid_collision ( Sak och annat ) { /*hantera rymdskepp-asteroidkollision*/ } void spaceship_collision ( Thing & other ) { /*handle Spaceship-spaceship collision*/ } offentliga : Rymdskepp () : Sak ( cid ) {} statisk void initCases (); statisk konst uint4 cid ; // klass-id }; Thing :: CollisionHandlerMap Thing :: collisionCases ; const uint4 Asteroid :: cid = typeid ( Asteroid ). hash_code (); const uint4 Rymdskepp :: cid = typeid ( Rymdskepp ). hash_code (); void Asteroid::initCases () { addHandler ( cid , cid , ( CollisionHandler ) & Asteroid :: asteroid_collision ); addHandler ( cid , Rymdskepp :: cid , ( CollisionHandler ) & Asteroid :: spaceship_collision ); } void Spaceship::initCases () { addHandler ( cid , Asteroid :: cid , ( CollisionHandler ) & Rymdskepp :: asteroid_collision ); addHandler ( cid , cid , ( CollisionHandler ) & Rymdskepp :: spaceship_collision ); } int main () { Asteroid :: initCases (); rymdskepp :: initCases (); Asteroid a1 , a2 ; Rymdskepp s1 , s2 ; a1 . kollideraMed ( a2 ); a1 . kollidera med ( s1 ); s1 . kollideraMed ( s2 ); s1 . kollideraMed ( a1 ); }yomm11-biblioteket [10] låter dig automatisera detta tillvägagångssätt.
I sin bok The Design and Evolution of C++ nämner Stroustrup att han gillar konceptet med multimetoder och att han övervägde att implementera dem i C++, men hävdar att han inte kunde hitta ett exempel på en effektiv (i jämförelse) med virtuella funktioner) att implementera dem och lösa några möjliga tvetydighetsproblem. Han hävdar vidare att även om det skulle vara trevligt att implementera stöd för detta koncept, kan det uppskattas genom dubbel sändning eller en typbaserad uppslagstabell som beskrivs i C/C++-exemplet ovan, så denna uppgift har låg prioritet i utvecklingen av framtida versioner av språket. . [elva]
Stöd för multimetoder på andra språk via tillägg:
Multiparametertypklasser i Haskell och Scala kan också användas för att emulera multimetoder.