Dziedziczenie a kompozycja - techniki projektowe. (IWZ wftg 03XII2004)
Rozszerzając projektowany system często dodajemy nową klasę. Niekiedy jest to klasa stworzona od podstaw, innym razem wiemy, że jakiś rodzaj funkcjonalności podobny do tej klasy już istnieje i możemy go wykorzystać, by nie odkrywać od nowa Ameryki. Być może zrealizowaliśmy wcześniej taką klasę , albo klasa taka jest w bibliotece albo stanowi część systemu. Nie musimy dysponować kodem źródłowym tej klasy, może to być biblioteka wykonywalna, np. plik typu .dll czy .exe. Ogólnie biorąc chcemy wykorzystać istniejącą klasę i tak ją obudować/rozbudować, by dostosować do naszych celów. Można to zrobić na dwa sposoby:
dziedziczenie - „is” nowa klasa jest i starą klasą - jak to w dziedziczeniu
kompozycja - „has” nowa klasa zawiera starą klasę (ściśle: jest w relacji związku),
bo nie zawiera klasy jako wewnętrznej a odwołuje się do tu utworzonego jej obiektu.
W pierwszym przypadku nowa klasa dziedziczy po istniejącej (nadklasie). Pozwala to na wykorzystanie już istniejących metod nadklasy, bez zmian, jeśli spełniają dobrze swoją rolę. Tak robimy dziedzicząc po klasie Form, gdy definiujemy swoją formę w programie VB .NET. Jeśli pewna metoda nadklasy jest potrzebna, ale jej funkcjonalność nie jest pełna, przedefiniujemy ją dzięki polimorfizmowi. Często dodajemy też zupełnie nowe metody.
W drugim przypadku nasza klasa obudowuje klasę istniejącą, by ją wykorzystać, gdy potrzeba jej usług. Wtedy użytkownik być może myśli, że wywołuje metody wykorzystywanej przez nas klasy, ale są to w istocie nasze funkcje o tej samej nazwie, które następnie (być może po dodatkowej obróbce) wywołają metody tejże klasy. Przecież po to zanurzyliśmy w naszej klasie tamten obiekt, by skorzystać z jego funkcjonalności i dodać do tego coś jeszcze.
Technika ta polega na umieszczeniu w definicji naszej klasy referencji (pole / zmienna obiektowa) do obiektu wykorzystanej klasy, utworzeniu w konstruktorze naszej klasy obiektu wykorzystywanej klasy i wywoływaniu jego metod, gdy są nam potrzebne, by obiekt robił dla nas to co umie.
Zaletą kompozycji jest to, że w obiekcie naszej klasy możemy umieścić odwołania (i musimy wygenerować) do wielu obiektów i to różnych klas. W rozwiązaniu poprzednim wymagało by to dziedziczenia wielobazowego - nierealizowalnego w większości języków.
Kiedy stosować dziedziczenie a kiedy kompozycję?
Zależy to np. od światopogladu programistycznego, czyli może być rodzajem wyznawanego dogmatyzmu - wtedy nie ma dyskusji.
Zależy od smaku, wyczucia i elegancji - czyli doświadczenia programistycznego.
Wytyczne:
Jeśli wykorzystywana klasa ma wiele metod, które są używane przez jakiegoś usługobiorcę (np. metody formy wołane są przez system operacyjny, gdy zmienia się rozmiary okienka czy je przesuwa), a my nie chcemy ingerować w te metody i pozostawić je bez zmian, wtedy stosujemy dziedziczenie.
Trzeba pamiętać, że stosując kompozycję musimy zdublować definicje wszystkich metod zanurzonego obiektu, które będą używane przez zewnętrznych użytkowników naszej klasy - jeśli jest ich dużo, to kłopot.
Jeśli nasza klasa ma zawierać wiele obiektów, to tylko kompozycja stanowi możliwe rozwiązanie.
Wszystko co da się zrobić dziedziczeniem da się zrobić kompozycją, ale nie na odwrót. Nie znaczy to, że stosowanie kompozycji zawsze jest sensowne.
Klasa KluczArrayList (słowo Klucz podkreśla polskie pochodzenie klasy )
jest przykładem rozbudowania funkcjonalności dynamicznej tablicy ArrayList (wbudowanej w system VB .NET) o możliwość posługiwania się kluczem w dostępie do elementów. W tym celu powstał dodatkowo interfejs IKluczArrayList. Do konstrukcji tej klasy wykorzytałem metodę kompozycji a nie dziedziczenia. Można spróbować i tę drugą ...