Kompozycja a dziedziczenie
Część sekcji Programowanie obiektowe ścieżki Java w Coddy. Lekcja 47 z 87.
Dziedziczenie tworzy relację „jest”: Dog jest Animal. Ale co, jeśli klasa potrzebuje funkcjonalności czegoś, czym nie jest? Właśnie tu wkracza kompozycja, ustanawiając zamiast tego relację „ma”.
Przy kompozycji klasa zawiera instancje innych klas jako pola, zamiast po nich dziedziczyć:
// Podejście oparte na dziedziczeniu: Car JEST Engine (nie ma sensu)
class Car extends Engine { }
// Podejście oparte na kompozycji: Car MA Engine (ma sens)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}Kompozycja oferuje kilka zalet. Możesz zmienić skomponowany obiekt w czasie działania, na przykład wymieniając silniki.
Możesz także łączyć zachowania z wielu klas, na co dziedziczenie nie pozwala w Javie. Skomponowana klasa udostępnia tylko wybrane przez siebie metody, zapewniając lepszą enkapsulację.
Praktyczna wskazówka: używaj dziedziczenia, gdy zachodzi rzeczywista relacja „jest czymś” i podklasa faktycznie stanowi wyspecjalizowaną wersję klasy nadrzędnej.
Używaj kompozycji, gdy potrzebujesz skorzystać z funkcjonalności innej klasy, nie będąc jednocześnie jej typem. Wielu doświadczonych programistów kieruje się zasadą: „przedkładaj kompozycję nad dziedziczenie”, ponieważ prowadzi to do bardziej elastycznego i łatwiejszego w utrzymaniu kodu.
Wyzwanie
ŁatwyZbudujmy system komputerowy z użyciem kompozycji, aby zademonstrować relację "has-a". Zamiast dziedziczyć klasę Computer po jej komponentach (co nie miałoby sensu), komputer będzie zawierać części potrzebne do działania.
Podzielisz kod na trzy pliki:
Processor.java: Utwórz klasę reprezentującą procesor CPU. Klasa Processor ma dwa prywatne pola:brand(String) ispeedGHz(double). Dodaj konstruktor inicjalizujący oba pola oraz metody pobierające wartość każdego z nich. Dodaj metodęprocess(), która zwraca String:[brand] processing at [speedGHz] GHzMemory.java: Utwórz klasę reprezentującą pamięć RAM. Klasa Memory ma dwa prywatne pola:type(String, na przykład "DDR4" lub "DDR5") isizeGB(int). Dodaj konstruktor, metody pobierające wartości pól oraz metodęload(), która zwraca:Loading [sizeGB]GB [type] memoryComputer.java: To właśnie tutaj kompozycja pokazuje swoje zalety! Klasa Computer powinna mieć obiekty Processor i Memory jako prywatne pola. Nie rozszerza ich, lecz je zawiera. Dodaj konstruktor przyjmujący oba komponenty. Dodaj metodęboot(), która zwraca wielowierszowy String pokazujący uruchamianie komputera z użyciem jego komponentów:Dodaj również metodęBooting computer... [processor.process() result] [memory.load() result] System ready!getSpecs(), która zwraca:Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]Main.java: Uruchom swój złożony system! Otrzymasz cztery dane wejściowe: markę procesora (String), szybkość procesora (double), typ pamięci (String) i rozmiar pamięci (int).Utwórz obiekty Processor i Memory z tymi wartościami, a następnie połącz je w obiekt Computer. Wypisz wynik wywołania
boot(), a potem wypisz wynik wywołaniagetSpecs().
Otrzymasz kolejno cztery dane wejściowe: markę procesora, szybkość procesora (GHz), typ pamięci i rozmiar pamięci (GB).
Zwróć uwagę, jak Computer deleguje zadania swoim komponentom, zamiast próbować nimi być. Na tym polega siła kompozycji! Computer może korzystać z dowolnego obiektu Processor lub Memory, który mu przekażesz, dzięki czemu projekt jest elastyczny i realistyczny.
Spróbuj swoich sił
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Odczytaj dane wejściowe
String processorBrand = scanner.nextLine();
double processorSpeed = Double.parseDouble(scanner.nextLine());
String memoryType = scanner.nextLine();
int memorySize = Integer.parseInt(scanner.nextLine());
// TODO: Utwórz obiekt Processor z podaną marką i prędkością
// TODO: Utwórz obiekt Memory z podanym typem i rozmiarem
// TODO: Utwórz obiekt Computer, łącząc obiekty Processor i Memory
// TODO: Wypisz wynik boot()
// TODO: Wypisz wynik getSpecs()
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe thisMetodyPola (atrybuty)Metoda konstruktoraPrzeciążanie konstruktoraPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawy dziedziczenia (extends)Słowo kluczowe superPrzesłanianie metod (@Override)Łańcuch wywołań konstruktorówKlasa ObjectDziedziczenie jedno- i wielopoziomoweDlaczego Java nie obsługuje wielodziedziczenia klasPodsumowanie – hierarchia pracowników7Metody specjalne i klasa Object
Metoda toString()equals() i hashCode()Metoda clone()compareTo() i ComparableInterfejs ComparatorPodsumowanie — własne sortowanie10Obsługa wyjątków w programowaniu obiektowym
Hierarchia klas wyjątkówWłasne wyjątkiBłędy sprawdzane a niesprawdzaneWzorzec try-with-resourcesPodsumowanie – zweryfikowany użytkownik2Modyfikatory dostępu i enkapsulacja
Przegląd poziomów dostępuMetody pobierające i ustawiająceUkrywanie informacjiSłowo kluczowe finalPodsumowanie — menedżer kont bankowych5Polimorfizm
Podstawy przeciążania metodPrzesłanianie metod (w czasie wykonywania)Rzutowanie w górę i w dółOperator instanceofKlasy i metody abstrakcyjnePowtórka — kalkulator figur8Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieAgregacja a kompozycjaKlasy wewnętrzne, zagnieżdżone i anonimoweEnumy i metody enumówRekordy (Java 16+)Klasy zapieczętowane (Java 17+)11Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec BuilderWzorzec ObserverWzorzec Strategy3Właściwości klas i składowe statyczne
Zmienne instancji a zmienne statyczneMetody statyczneBloki statyczneStałe (static final)Powtórka – Counter i Utility6Interfejsy i klasy abstrakcyjne
Wprowadzenie do interfejsówImplementowanie interfejsówImplementacja wielu interfejsówMetody domyślne i statyczne w interfejsieKlasy abstrakcyjne a interfejsyInterfejsy funkcyjnePodsumowanie — system płatnościPoćwicz samodzielnie: Kompilator Java online