Materiały pomocnicze
Java
- Bruce Eckel, Thinking in Java
- Java™ Foundation Classes in a Nutshell: A Deskop Quick Reference
- Przykładowe kody źródłowe
- Technologie klienckie
Projekty
- Projektowanie zorientowane obiektowo
- Zasady SOLID — przykłady w w języku Java oraz C#
- Interfejs, czy klasa abstrakcyjna w Java
Kompozycja czy dziedziczenie
Po odkryciu polimorfizmu łatwo jest pomyśleć, iż ponieważ jest on tak sprytnym narzędziem, więc należy stosować dziedziczenie wszędzie. Może to mieć jednak negatywny wpływ na nasze projekty. W istocie wybieranie dziedziczenia jako pierwszego sposobu w sytuacji, gdy wykorzystujemy istniejącą klasę dla stworzenia nowej, powoduje, że sprawy niepotrzebnie się komplikują.
Lepszym wyjściem jest, gdy wybór nie jest oczywisty, wybieranie jako pierwszego przybliżenia kompozycji. Kompozycja nie wymusza budowania projektu jako hierarchii dziedziczenia. Jest także bardziej elastyczna, ponieważ możliwa jest dynamiczna zmiana typów (a przez to zachowania) składowych klasy, podczas gdy w przypadku dziedziczenia dokładny typ musi być znany na etapie kompilacji. Ilustruje to poniższy przykład:
//Dynamiczna zmiana zachowania obiektu poprzez użycie kompozycji. abstract class Actor { public abstract void act(); } class HappyActor extends Actor { public void act() { System.out.println("HappyActor"); } } class SadActor extends Actor { public void act() { System.out.println("SadActor"); } } class Stage { private Actor actor = new HappyActor(); public void change() { actor = new SadActor(); } public void performPlay() { actor.act(); } } public class MainClass { public static void main(String[] args) { Stage stage = new Stage(); stage.performPlay(); //Wypisuje "HappyActor" stage.change(); stage.performPlay(); //Wypisuje "SadActor" } }
Obiekt typu Stage zawiera referencję do typu Actor, która jest inicjalizowana na obiekt klasy HappyActor (linia 19). Oznacza to, iż metoda
performPlay()
działa w pożądany sposób. Ponieważ jednak referencja może w czasie wykonywania zostać związana z innym obiektem, możemy więc podstawić do actor referencję do obiektu SadActor, zmieniając dzięki temu zachowanie metodyperformPlay()
. Zyskujemy zatem dynamiczną elastyczność czasu wykonania. Dla kontrastu: nie możemy zdecydować się na inny sposób dziedziczenia w czasie wykonywania programu - musi to być jednoznacznie określone w chwili kompilacji.Generalną wskazówką jest: "używaj dziedziczenia dla wyrażania różnic w zachowaniu, zaś pól dla wyrażania zmian stanu". W powyższym przykładzie wykorzystuje się obie techniki: stworzone zostały dwie różne klasy potomne dla wyrażenia różnic w zachowaniu się metody
act()
, natomiast obiekt Stage wykorzystuje kompozycję dla umożliwienia zmiany swego stanu. W tym przypadku zmiana stanu powoduje zmianę w zachowaniu.
- Wyjaśnienie powyższego przykładu dla opornych ☺
- Porównanie ww. mechanizmów można również znaleźć na stronie blog.helion.pl lub Sarven Dev