
Dziedziczenie vs kompozycja
Kiedy blisko 10 lat temu zacząłem trochę bardziej serio programować obiektowo, byłem „zauroczony” trzema paradygmatami stojącymi za tym podejściem. Dla przypomnienia są to (za Wikipedią):
- abstrakcja,
- hermetyzacja,
- polimorfizm.
Każdy z nich jest ważny i w pewnym sensie definiuje to, jak postrzegamy programowanie obiektowe. Z biegiem lat jednak dostrzegłem wady ostatniego paradygmatu. Dziedziczenie to diabelska pokusa, której trudno się oprzeć. Czasem próbujemy używać go w takich miejscach, w których można by ze spokojem zastosować inne, bardziej eleganckie i satysfakcjonujące rozwiązanie. Do grona alternatyw z pewnością można zaliczyć podejście oparte na kompozycji, które zyskuje na znaczeniu zwłaszcza wtedy, gdy w projekcie używamy IoC.
Co złego jest w dziedziczeniu?
Jeśli tworzymy rozwiązanie wykraczające poza pojedynczy projekt (tworzymy własne SDK, framework), to istnieje spore prawdopodobieństwo, że dziedziczenie wówczas się nie sprawdzi. W pewnym momencie dojdzie do takiej sytuacji, że potencjalne wdrożenie będzie wymagało zmian w SDK, co może spowodować konflikty w innych korzystających z niego projektach. Równie prawdopodobne jest, że odziedziczymy klasę, w której będziemy modyfikować, lub też nadpisywać, część z wcześniej utworzonych metod. Takie działanie nie jest mile widziane i może w łatwy sposób doprowadzić do złamania niektórych punktów z mnemonika SOLID. Wykorzystując rozbudowane dziedziczenie, generujemy sporo innych problemów:
- Znacząco utrudniamy tworzenie testów jednostkowych lub całkowicie uniemożliwiamy ich napisanie.
- Utrudniamy sobie życie przy debugowaniu, w sytuacji gdy metody i właściwości naszego obiektu znajdują się na różnych poziomach dziedziczenia.
- Łamiemy postanowienia mnemonika SOLID.
- Tworzymy kod, którego zachowanie jest trudniejsze do przewidzenia.
- Niwelujemy możliwość wprowadzenia interfejsów, co powoduje, że stajemy się bardziej uzależnieni od konkretnej implementacji.
W czym kompozycja jest lepsza od dziedziczenia?
Pewną ciekawą alternatywą jest podejście oparte na kompozycji, które niweluje większość przytoczonych problemów. Stosując ją, możemy świetnie wpasować się w realia SOLID. Tworzymy w tym przypadku klasy, które często implementują konkretne interfejsy. Jeśli we wdrożeniu nie odpowiada nam określona implementacja generatora raportów, to po prostu tworzymy własną, która implementuje metody wcześniej utworzonego interfejsu i jednocześnie wykorzystuje inne, dostępne w naszym projekcie klasy.
W tym przypadku kluczem do sukcesu jest pilnowanie zasady pojedynczej odpowiedzialności poszczególnych klas. Łatwiej jest nam utworzyć pojedynczą nową implementację klasy rozwiązującej konkretny problem i zarejestrować ją w kontenerze IoC, niż głowić się nad tym, co autor klasy bazowej miał na myśli.
Czy powinniśmy zrezygnować z dziedziczenia? Czy jest dla niego jakieś sensowne zastosowanie?
Czy bazując na tym, co napisano powyżej, powinniśmy teraz zabrać swoje wszystkie zabawki z piaskownicy zwanej dziedziczenie i wynieść się do obozu zwolenników kompozycji? Cóż, to zależy od sytuacji. Jest wiele obszarów programowania, w których kompozycja sprawdza się lepiej, ponieważ takie podejście zwiększa dość mocno naszą elastyczność. Z drugiej strony są pewne problemy, w których rozwiązaniu lepiej sprawdzi się stare, poczciwe dziedziczenie. Jednym z nich jest problem drzewa obiektów.
Jeśli spojrzycie na popularne języki programowania wykorzystujące UI, to dostrzeżecie pewną prawidłowość. W większości z nich layout jest tworzony za pomocą drzewa obiektów, co strukturalnie przypomina język XML. Mamy element główny, a później kolejne zagnieżdżenia. Podobnie sytuacja wygląda z perspektywy klas. Mamy klasę główną typu View, Element, UIObject (itd.) oraz klasy potomne z niej dziedziczące.
Łatwo sobie wyobrazić np. taką hierarchię:
Element => Control => Button => RadioButton
Element => Control => Button => ImageButton itd.
W takich sytuacjach dziedziczenie sprawdza się świetnie, ponieważ kolejne elementy interfejsu zyskują istotne właściwości od swoich rodziców. Ponadto, przeszukując drzewo obiektów, możemy szukać kolejnych obiektów typu „Element”, a później dokonywać odpowiedniego rzutowania, na interesujący nas obiekt docelowy. Czy kompozycja sprawdziłaby się w tym miejscu? Być może, ale mnie bardziej pasuje tu dziedziczenie. W tym przypadku po prostu działa.
Szukasz informacji? Kliknij poniżej:
Zobacz nasze propozycje
-
(53.40 zł najniższa cena z 30 dni)
56,07 zł89,00 zł -
(23.94 zł najniższa cena z 30 dni)
25,54 zł39,90 zł -
(ebook)
(29.40 zł najniższa cena z 30 dni)
31,36 zł49,00 zł -
(41.40 zł najniższa cena z 30 dni)
44,16 zł69,00 zł -
(ebook)
(11.99 zł najniższa cena z 30 dni)
11,99 zł39,90 zł -
(59.40 zł najniższa cena z 30 dni)
59,40 zł99,00 zł -
(53.40 zł najniższa cena z 30 dni)
56,96 zł89,00 zł -
(47.40 zł najniższa cena z 30 dni)
48,19 zł79,00 zł -
(ebook)
(41.40 zł najniższa cena z 30 dni)
44,16 zł69,00 zł -
(77.40 zł najniższa cena z 30 dni)
82,56 zł129,00 zł -
(35.40 zł najniższa cena z 30 dni)
37,76 zł59,00 zł -
(35.40 zł najniższa cena z 30 dni)
36,58 zł59,00 zł -
(23.94 zł najniższa cena z 30 dni)
25,14 zł39,90 zł -
(41.40 zł najniższa cena z 30 dni)
44,16 zł69,00 zł -
(35.40 zł najniższa cena z 30 dni)
37,76 zł59,00 zł -
(59.40 zł najniższa cena z 30 dni)
63,36 zł99,00 zł -
(53.40 zł najniższa cena z 30 dni)
55,18 zł89,00 zł -
(53.40 zł najniższa cena z 30 dni)
53,40 zł89,00 zł -
(53.40 zł najniższa cena z 30 dni)
56,07 zł89,00 zł -
(22.20 zł najniższa cena z 30 dni)
23,68 zł37,00 zł