Dwie hierarchie
Z nauką nowych rzeczy jest trochę jak z oglądaniem reklam - nagle dowiadujesz się, że masz nowe potrzeby i to co było do tej pory przestaje wystarczać. A Coż może być takie fajne?. Gdy błądzący programista przedziera się przez materiały o obiektówce to zazwyczaj w sąsiedztwie słowa "Polimorfizm" znajduje się jakiś przykład gdzie kot i pies dziedziczą z klasy Animal czy coś w ten deseń.
Okazuje się, że te przykłady przedstawiają coś co nazywa się "single dispatch" i to coś ma ograniczenia, których nie ma bogatszy mechanizm zwany Double Dispatch lub Multi Dispatch. Może to dla większosci oczywiste ale mnie o tym w szkole nie uczyli.
A jeśli ktoś tak jak ja został niedopieszczony przez system edukacji to kod jak zwykle najlepiej pokaże o co chodzi. ( a jeśli ktoś jednak woli wikipedię to :
)kod
Użyjemy dwóch hierarchii klas.
Pierwsza jest ludzka i zawiera
- Klasę Human - nie chodzi humanistę tylko ogólnie o człowieka
- Klasa Hipster dziedziczy z Human - Hipster to ktoś kto deklaruje swoją niezależność od "mainstreamu" głównie poprzez używanie mainstreamowych aplikacji społecznościowych
- Klasa AntyHipster dziedziczy z Human - Antyhipster to ktoś kto jest takim hipsterem, że aż nie używa fejsbuka - wspomni o tym w przeciągu pierwszych 20 sekund rozmowy
Druga hierarchia to martwa natura i zawiera :
- Klasę Drink - czyli napój
- Klasa Vodka dziedziczy z Drink - czyli wóda
- Klasa OrangeJuice dziedziczy z Drink - Litr soku pomarańczowego zawiera więcej cukru niż litr Coli --> ile cukru w cukrze
abstract class Human { def consume(drink: Drink):String } class Hipster extends Human{ def consume(drink: Drink) = s"hipster consuming drink : $drink" def consume(drink: Vodka) = s"hipster consuming vodka : $drink" def consume(drink: OrangeJuice) = s"hipstewr consuming Orange : $drink" } class AntyHipster extends Human { def consume(drink: Drink) = s"antyhipster consuming drink : $drink" def consume(drink: Vodka) = s"antyhipster consuming vodka : $drink" def consume(drink: OrangeJuice) = s"antyhipster consuming Orange : $drink" } class Drink class OrangeJuice extends Drink class Vodka extends Drink //deklaracje val hipster = new Hipster val antyhispter = new AntyHipster val juice = new OrangeJuice val vodka = new Vodka
Przypadek jeden Zero dispatch - mała elastyczność
Na samym początku przykład prosty jak cep. Metoda, która przyjmuje konkretną implementacje Human (Hipster) i konkretny napój (Vodka). Gdybyśmy chcieli obsłużyć wszystkie przypadki (Human,Drink) wtedy potrzebujemy 4 różne metody (bardzo niedobrze, bardzo bardzo źle)
def zeroDispatch(hipster:Hipster,vodka:Vodka)=hipster.consume(vodka) zeroDispatch(hipster, vodka) //> res0: String = hipster consuming vodka : Vodka
Trochę lepiej - Single dispatch
Co chcę zrobić : przekazać obiekt typu człowiek i ma się wywołać metoda z odpowiedniej implementacji. Czyli poniżej metoda spodziewa się po prostu Human i można tam przekazać i Hipstera i AntyHisptera - czyli klasyczny "Javowy Polimorfizm". Aby obsłużyć wszystkie kombinacje (Human,Drink) potrzebowałbym dwóch metod - po jednej na każdy napój.
def singleDispatch(human:Human,vodka:Vodka)=human.consume(vodka) val hipster : Human= new Hipster val antyhispter : Human = new AntyHipster singleDispatch(hipster, vodka) //> res0: String = hipster consuming drink : Vodka singleDispatch(antyhispter, vodka) //> res1: String = antyhipster consuming drink : Vodka
Multiple Dispatch - pierwsze podejscie z failem
I teraz ważne co chcę zrobić : chcę mieć jedną metodę która ani nie wie co to za Human ani co to za Drink i jak np. przekażę Hipstera i Vodkę
to się automatycznie wywoła metoda z Hipstera, która przyjmuje Vodke.
Proba 3..2...1..
val hipster : Human= new Hipster val antyhispter : Human = new AntyHipster val vodka : Drink= new Vodka def notWorkingMultipleDispatch(human:Human,drink:Drink)=human.consume(drink) notWorkingMultipleDispatch(hipster, vodka)//> res0: String = hipster consuming drink : Vodka notWorkingMultipleDispatch(antyhispter, vodka)//> res1: String = antyhipster consuming drink : Vodka
No i Ch** bo jakby ktoś nie zauważył wywołana jest generyczna metoda "consume(Drink)". W Javie można zrobić takie nieładne "instanceof" albo Vizytatora. W Scali można zrobić Pattern Matching albo Vizytatora.
Ewentualnie można zrobić takiego "niewidzialnego" Vizytatora co zrobimy dla ćwiczeń teraz bo nie mam innego pomysłu na zakończenie tego artykułu
Rzeźbimy
abstract class Human { def consume[A](drink: A)(implicit dispatcher: DrinkMultiDispatch[A]): String } class Hipster extends Human{ override def consume[A](drink: A)(implicit dispatcher: DrinkMultiDispatch[A]): String="hipster "+dispatcher.dispatch(drink) } trait DrinkMultiDispatch[A] { def dispatch(drink: A): String }
Idea polega na dołożeniu "czegoś" co będzie decydowało o tym jak obsłużyć konkretny napój. Ponieważ mamy implicity to będzie pojawiało się znikąd i user nawet nie zauważy.
implicit val udawaneMultipleDispatch = new DrinkMultiDispatch[Drink]{ def dispatch(drink: Drink) = drink match { case _:Vodka => "drinking vodka" case _:OrangeJuice => "drinking juice" } }
To jest takie oszukiwanie przez pattern matching. Próbowałem dopasować typ Dispatchera poprzez Generyk ale zawsze dostawałem "ambiguous implicit values" jak siedziały obok siebie [Vodka] i [OrangeJuice].
def workingMultipleDispatch(human:Human,drink:Drink)=human.consume(drink) workingMultipleDispatch(hipster, vodka) //> res3: String = hipster drinking vodka workingMultipleDispatch(hipster, juice) //> res4: String = hipster drinking juice workingMultipleDispatch(antyhipster, vodka) //> res5: String = antyhipster drinking vodka workingMultipleDispatch(antyhipster, juice) //> res6: String = antyhipster drinking juice
No i od bidy można powiedzieć, że to rozwiązanie uwalnia klasy "domenowe" od instanceof. Jeśli jest jakieś lepsze rozwiązanie to nie umiem go znaleźć. Zawsze trochę zabawy i edukacji.
A w ogóle skąd ten multi dispatch? Przeczytałem, że Clojure ma to z paczki ale podobno wolne strasznie.
* * *
Brak komentarzy:
Prześlij komentarz