Od kilku miesięcy piszę swoją pierwszą aplikację z wykorzystaniem Kotlina (Android) i miałem takie małe postanowienie, że zacznę umieszczać na blogu wpisy związane z tym językiem programowania. W końcu mi się udało i na pierwszy ogień pójdzie zagadnienie określane jako class delegation.

Class delegation jest ściśle powiązane ze wzorcem projektowym Delegation (zaskakujące 🤣), z którego mogliście już zupełnie nieświadomie korzystać podczas codziennej pracy. Delegation polega na przekazaniu odpowiedzialności za wykonanie pewniej operacji z jednego obiektu do drugiego. Jest on alternatywą dla dziedziczenia, które uważane jest za rozwiązanie mniej wydajne. A dlaczego, wyjaśnię już za chwilę.

Najpierw jednak chcę Wam pokazać jak mogłaby wyglądać ‚ręczna’ implementacja wspomnianego wzorca w Kotlinie:

W pierwszej kolejności definiujemy interfejs, który będzie implementowany zarówno przez obiekt delegujący zadanie (RegularProgrammer), jak również ten odpowiedzialny za jego wykonanie (JuniorProgrammer). Jest to wymóg konieczny, ponieważ obiekty te muszą wiedzieć jak się ze sobą komunikować. Wspólny interfejsu jasno określa czego mogą się po sobie spodziewać. Z jakich właściwości oraz funkcji będą korzystać.

Pierwszy obiekt (programista junior 😊) implementuje interfejs Programmer i definiuje zachowanie dla funkcji writeCode(). Sens całej zabawy widać jednak dopiero w drugim obiekcie, reprezentującym starszego stażem programistę. Uznał on, że przydzielone mu zadanie jest zbyt trywialne, dlatego postanowił oddelegować je do mniej doświadczonego kolegi.

W tym celu skorzystał z instancji obiektu JuniorProgrammer, który specjalizuje się w pisaniu aplikacji z wykorzystaniem Kotlina. Zamiast implementować własne zachowanie dla funkcji writeCode(), obiekt RegularProgrammer skorzystał z implementacji obiektu JuniorProgrammer. Jak sami widzicie, nie ma w tym nic przesadnie skomplikowanego 😉.

Ten sam rezultat może zostać osiągnięty poprzez proste dziedziczenie, połączone z wywołaniem implementacji klasy bazowej. Jest jednak kilka powodów, które przemawiają na korzyść delegacji i to właśnie nimi się teraz zajmiemy 😎.

 

Delegation vs Inheritance

 

Dziedziczenie jest podejściem lepiej znanym, a przez to również częściej stosowanym. W niektórych językach (takich jak Java) korzystanie z wzorca Delegation jest obciążone dodatkowo koniecznością generowania dużych ilość powtarzalnego kodu (jak to w Javie). Zupełnie inaczej sprawa ma się w Kotlinie, o czym będziecie mogli się za chwilę przekonać.

Niektórzy przedstawiciele naszej profesji uważają, że w wielu przypadkach dziedziczenie powinno zostać zastąpione przez delegowanie. Przemawia za tym kilka faktów:

– Klasy oznaczone operatorem final nie mogą być dziedziczone, a co za tym idzie w ich przypadku pozostaje nam tylko delegowania. W Kotlinie wszystkie klasy domyślnie oznaczone zostały jako final.

– W Javie mamy możliwość dziedziczenia tylko po jednej klasie, ale możemy delegować do dowolnej liczby obiektów.

– Nadpisując zachowania danej klasy nie zawsze będziemy mieli pewność, czy przypadkiem nie naruszyliśmy jakiejś delikatnej struktury.

– Korzystanie z delegowanie zmniejsza poziom zależności (copuling) pomiędzy obiektami. Im mniejszy poziom zależności, tym bardziej modularny staje się nasz projekt. To z kolei oznacza, że łatwiej będzie ponownie wykorzystać niektóre fragmenty, a także pisać do nich testy.

– Korzystanie z interfejsów ogólnie rzecz biorąc traktowane jest jako dobra praktyka (byle nie przesadzić 😆). Klient (czyli jakiś inny obiekt korzystający z interfejsu) nie powinien posiadać informacji o metodach, z których nie będzie korzystał. Interfejs ujawnia tylko metody / pola publiczne, a część prywatna danego obiektu pozostaje jedynie na użytek wewnętrzny.

Dla niektórych poważnym minusem delegowania jest konieczność tworzenia osobnych interfejsów, które szybko mogą wymknąć się spod kontroli. Moim zdaniem jest to słuszna uwaga, ale nie należy zapominać o jednej rzeczy. Class delegation jest tylko pewnym narzędziem, które sprawdzi się w określonych przypadkach (jak te wymienione powyżej). Nikt nie twierdzi, że korzystając z delegowania, należy całkowicie porzucić dziedziczenie.

 

Delegowanie w Kotlinie

 

Teraz pokażę Wam jak delegowanie można zaimplementować w Kotlinie. Przykład przedstawiony na samym początku wpisu jest jak najbardziej poprawny, jednak ten sam efekt można uzyskać za pomocą poniższego kody:

Prawda, że krócej? Z własnego doświadczenie wiem jednak, że poznanie dłuższej wersji pomaga w zrozumieniu co się za tym kryje. A idzie to tak… Korzystając z operatora ‚by’ informujemy kompilator, że wykonanie wszystkich metody zdefiniowanych w interfejsie Programmer, w przypadku obiektu RegularProgrammer powinno zostać oddelegowane do obiektu JuniorProgrammer.

Kompilator sprawdza, które metody z interfejsu Programmer nie zostały zaimplementowane w RegularProgrammer, a następnie uzupełnia je wywołaniami do obiektu JuniorProgrammer. Ten z kolei tworzony jest jednocześnie z obiektem RegularProgrammer.

Delegować w Kotlinie możemy jeszcze na kilka innych sposobów. Jednym z nich jest delegowanie do zmiennej dostępnej podczas tworzenia obiektu:

Zapis taki możemy oczywiście skrócić do:

Dalej mamy delegowanie do zmiennej zdefiniowanej w konstruktorze:

A także delegowanie do obiektu stanowiącego standardowy parametr konstruktora:

Jeżeli będziemy chcieli przekazać wykonanie zadań do kilku obiektów, to nic nie stanie nam na przeszkodzie. A będzie wyglądało to tak:

 

Słowo na drogę

 

Delegowanie jest wzorcem, który na początku nie sprawia wrażenia zbyt intuicyjnego. Jednak z czasem zaczyna się dostrzegać jego zalety i później jest już z górki 🤓. Mi osobiście przyswojenie tego wzorca przyszło stosunkowo łatwo. Mam już trochę doświadczenia z iOS’em i Swift’em, gdzie jest on stosowany od jakiegoś czasu. Wbrew pozorom Android i iOS, jako platformy mobilne, mają ze sobą sporo wspólnego 😉.

 

Link do obrazka tytułowego


 

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *