Skip to main content

Michal Valko : Projects

Double Dispatching

Double Dispatching je OOP design pattern používaný v Smalltalku a iných objektovo-orientovaných jazykoch na elegantné riešenie problému polymorfizmu s parametrami.

Problém?

Ten nastáva v prípade, ak objektu pošleme správu s istým stupňom neurčitosti (voľnosti). Napríklad prienik s nejakým objektom alebo pripočítanie k nejakému číslu. Vtedy na spracovanie správy potrebujeme určiť, kto sa za parametrom skrýva.

Ak pošleme objektu správu s parametrom, výsledné správanie je definované nejakou prislúchajúcou metódou v príjemcovi správy. Niekedy je správanie podmienené typom, či triedou parametra. V niektorých jazykoch, ktoré podporujú case konštrukcie (C++, Java,...) by jedným z možných riešení mohlo byť rozhodnúť sa (v case) podľa typu parametra. V Smalltalku však žiadne case, či switch nemáme. Ak sa pridá nová trieda pre parameter, museli by sme modifikovať kvantum if-ov.

Riešenie

Spočíva v postupnom znižovaní stupňa neurčitosti. V Smalltalku je lepšie použiť polymorfickú prirodzenosť jazyka a použiť techniku známu ako Double Dispatching. Tá zahŕňa pridanie druhej metódy (secondary method) všetkým triedam potenciálnym parametrom a potom jej zavolaním z pôvodnej metódy s prijímateľom ako parameter.

Jednou veľmi peknou vlastnosťou Double Dispatchingu je, že kompletne nahradzuje podmienkové konštrukcie pre robustnejší kód. Polymorfický dispatching sa postará o rozhodnutia.

Použitie

Použitie vysvetlíme na príklade. Konkrétne použijeme definíciu tried a metód, použitých v SmallTalku na hierarchii tried počínajúc Number.

Trieda Integer a jej podtriedy používajú double dispatch na implementovanie niektorých základných aritmetických operácií. Ukážeme si na operácii (metóde) #+ (plus).

#+ je definované Integerom takto:

Integer>>+ aNumber
^aNumber addToInteger: self

Vnútri tejto metódy, už vieme, že aNumber sa chce pripočítať k príjimateľovi (to akože Integeru). Aby sme predišli písaniu kódu, ktorý by sa rozhodoval podľa parametra aNumber, môžeme jednoducho požiadať aNumber, aby tento výpočet spravil za nás.

ArithmeticValue>>addToInteger: anInteger
^anInteger retry: #+ coercing: self

Každá podtrieda Integeru, ktorá požaduje špeciálne pripočítavnie, implementuje svoju vlastnú #addToInteger. Napríklad trieda Fraction class vie, ako si má k sebe ten Integer pripočítať:

addToInteger: anInteger
^Fraction
numerator: anInteger denominator + numerator denominator: denominator

V podstate takto môžeme rozdeliť metódy na tri druhy:

  • primárne operácie
  • double dispatching metódy
  • forward metódy

Výhody

je oveľa viac objektovo orientovaný ako klasický prístup
je rýchlejší: namiesto testovania "typu", pošle iba jednu alebo dve správy
metódy sú o dosť menšie (viacmenej jednoriadkové), aj keď je ich trochu viac :)
je to celé viac otvorené. Pri použití DD je jednoduchšie pridávať nové triedy. Nie je nutné meniť už existujúci kód.

Koľko to stojí?

Pridanie novej triedy takto znamená pridanie správy do každej z ostatných tried, v najhoršom prípade N2 metód pre N tried. Napriek tomu, celkový počet riadkov kódu sa nám viditeľne nezväčší.

Porovnanie (36 tried, 5 operátorov):

predtým DD bez redukcie DD s redukciou
základné metódy 180 180 180
korekcie 73 0 0
Double Dispatch 0 6480 221
Celkový počet 253 6660 401

Linky