Komposition vs. Vererbung
Teil des Abschnitts Objektorientierte Programmierung der Java-Journey von Coddy. Lektion 47 von 87.
Vererbung stellt eine „ist-ein“-Beziehung her: Ein Dog ist ein Animal. Aber was ist, wenn eine Klasse Funktionalität von etwas benötigt, das sie nicht ist? Hier kommt die Komposition ins Spiel, die stattdessen eine „hat-ein“-Beziehung herstellt.
Bei der Komposition enthält eine Klasse Instanzen anderer Klassen als Felder, anstatt sie zu erweitern:
// Vererbungsansatz: Car IS an Engine (ergibt keinen Sinn)
class Car extends Engine { }
// Kompositionsansatz: Car HAS an Engine (ergibt Sinn)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}Komposition bietet mehrere Vorteile. Du kannst das zusammengesetzte Objekt zur Laufzeit ändern, beispielsweise indem du Motoren austauschst.
Du kannst auch Verhaltensweisen aus mehreren Klassen kombinieren, was die Vererbung in Java nicht erlaubt. Die zusammengesetzte Klasse stellt nur die Methoden bereit, für die sie sich entscheidet, und bietet dadurch eine bessere Kapselung.
Eine praktische Richtlinie: Verwende Vererbung, wenn eine echte „ist-ein“-Beziehung besteht und die Unterklasse tatsächlich eine spezialisierte Version der übergeordneten Klasse darstellt.
Verwende Komposition, wenn du die Funktionalität einer anderen Klasse nutzen musst, ohne von diesem Typ zu sein. Viele erfahrene Entwickler folgen dem Prinzip: „Komposition vor Vererbung bevorzugen“, weil dies zu flexiblerem und leichter wartbarem Code führt.
Aufgabe
EinfachErstellen wir ein Computersystem mithilfe von Komposition, um die „has-a“-Beziehung zu veranschaulichen. Anstatt dass ein Computer von seinen Komponenten erbt (was keinen Sinn ergeben würde), wird dein Computer die Teile enthalten, die er für seine Funktion benötigt.
Du wirst deinen Code auf drei Dateien verteilen:
Processor.java: Erstelle eine Klasse, die eine CPU repräsentiert. Ein Processor besitzt zwei private Felder:brand(String) undspeedGHz(double). Füge einen Konstruktor hinzu, der beide Felder initialisiert, sowie Getter-Methoden für jedes Feld. Füge eineprocess()-Methode hinzu, die einen String zurückgibt:[brand] processing at [speedGHz] GHzMemory.java: Erstelle eine Klasse, die RAM repräsentiert. Ein Memory besitzt zwei private Felder:type(String, etwa "DDR4" oder "DDR5") undsizeGB(int). Füge einen Konstruktor, Getter und eineload()-Methode hinzu, die Folgendes zurückgibt:Loading [sizeGB]GB [type] memoryComputer.java: Hier zeigt sich die Stärke der Komposition! Deine Computer-Klasse sollte einen Processor und einen Memory als private Felder besitzen. Sie erweitert diese nicht, sondern enthält sie. Füge einen Konstruktor hinzu, der beide Komponenten entgegennimmt. Füge eineboot()-Methode hinzu, die einen mehrzeiligen String zurückgibt, der das Starten des Computers durch die Verwendung seiner Komponenten zeigt:Füge außerdem eineBooting computer... [processor.process() result] [memory.load() result] System ready!getSpecs()-Methode hinzu, die Folgendes zurückgibt:Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]Main.java: Erwecke dein zusammengesetztes System zum Leben! Du erhältst vier Eingaben: die Marke des Prozessors (String), die Geschwindigkeit des Prozessors (double), den Speichertyp (String) und die Speichergröße (int).Erstelle mit diesen Werten einen Processor und einen Memory und füge sie anschließend zu einem Computer zusammen. Gib das Ergebnis des Aufrufs von
boot()aus und gib danach das Ergebnis vongetSpecs()aus.
Du erhältst vier Eingaben in dieser Reihenfolge: Prozessormarke, Prozessorgeschwindigkeit (GHz), Speichertyp und Speichergröße (GB).
Beachte, wie der Computer Arbeit an seine Komponenten delegiert, anstatt zu versuchen, selbst eine davon zu sein. Das ist die Stärke der Komposition! Der Computer kann jeden Processor oder Memory verwenden, den du ihm übergibst, wodurch das Design flexibel und realistisch wird.
Probier es selbst
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Eingaben lesen
String processorBrand = scanner.nextLine();
double processorSpeed = Double.parseDouble(scanner.nextLine());
String memoryType = scanner.nextLine();
int memorySize = Integer.parseInt(scanner.nextLine());
// TODO: Erstelle einen Processor mit der Marke und der Geschwindigkeit
// TODO: Erstelle einen Memory mit dem Typ und der Größe
// TODO: Erstelle einen Computer durch Zusammensetzen von Processor und Memory
// TODO: Gib das Ergebnis von boot() aus
// TODO: Gib das Ergebnis von getSpecs() aus
}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienEinführung in die OOPKlassen vs. ObjekteDas Schlüsselwort thisMethodenFelder (Attribute)KonstruktormethodeÜberladen von KonstruktorenRückblick – Einfacher Taschenrechner4Vererbung
Grundlegende Vererbung (extends)Das Schlüsselwort superÜberschreiben von Methoden (@Override)KonstruktorverkettungDie Object-KlasseEinfach- und mehrstufige VererbungWarum keine Mehrfachvererbung von KlassenRückblick – Mitarbeiterhierarchie7Spezielle Methoden & Object-Klasse
toString()-Methodeequals() und hashCode()clone()-MethodecompareTo() und ComparableComparator-SchnittstelleRückblick – Benutzerdefinierte Sortierung2Zugriffsmodifikatoren & Kapselung
Übersicht der ZugriffsebenenGetter- und Setter-MethodenInformationsverbergungDas Schlüsselwort finalRückblick – Bankkontoverwaltung5Polymorphismus
Grundlagen der MethodenüberladungMethodenüberschreibung (Laufzeit)Upcasting und DowncastingDer instanceof-OperatorAbstrakte Klassen und MethodenRückblick – Formrechner8Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungAggregation vs. KompositionInnere, verschachtelte & anonyme KlassenEnums und Enum-MethodenRecords (Java 16+)Versiegelte Klassen (Java 17+)11Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterBuilder-MusterObserver-MusterStrategie-Muster14Abschließende Herausforderungen
E-Learning-PlattformBankingsystemEntwicklung einer SpielfigurFahrzeugvermietungsdienst3Klassen-Props & statische Member
Instanz- vs. statische VariablenStatische MethodenStatische BlöckeKonstanten (static final)Wiederholung – Counter & Utility6Schnittstellen & abstrakte Klassen
Einführung in InterfacesInterfaces implementierenMehrere Interfaces implementierenDefault & Static in InterfacesAbstrakte Klassen vs. InterfacesFunktionale InterfacesRückblick – ZahlungssystemÜbe selbstständig: Online-Java-Compiler