Warum man nicht von JFrame/JDialog erben sollte

F

Firephoenix

Gast
Es ist doch verwunderlich wie oft man so etwas hier sieht:

Java:
public class Gui extends JFrame{

	public Gui(){
		super("Toller titel");
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		getContentPane().add(new JButton("sinnloser button"));
		pack();
		setVisible(true);
	}
}

Sieht irgendwie bekannt aus, oder?
Und: Normalerweise würgt man die VM auch nicht mit
Code:
System.exit(0);
respektive
Code:
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
ab, sondern ruft dispose() auf.

Tatsächlich ist der Code genauso furchtbar wie das hier:
Java:
public class Buchregal extends ArrayList<String>{

	public Buchregal(){
		add("Spiele programmieren mit Java");
		add("Spring im Einsatz");
		add("Handbuch der Java Programmierung");
	}
}

Und mal ehrlich, wer würde auf die Idee kommen, so etwas anzustellen?

Da schreibt man ein Buchregal, das 3 Buchtitel enthält, doch lieber so oder?
(zugegeben es ist kein schönes Buchregal das nur die Titel kennt)

Java:
public class Buchregal{

	private ArrayList<String> buchtitel;
	
	public Buchregal(){
		buchtitel = new ArrayList<String>();
		buchtitel.add("Spiele programmieren mit Java");
		buchtitel.add("Spring im Einsatz");
		buchtitel.add("Handbuch der Java Programmierung");
	}
}

Und genau so kann man auch mit der GUI-Klasse verfahren:

Java:
public class Gui{

	private JFrame frame;
	
	public Gui(){
		frame = new JFrame("Toller titel");
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.getContentPane().add(new JButton("sinnloser button"));
		frame.pack();
		frame.setVisible(true);
	}
}

Nur was hat man jetzt eigentlich gemacht?

In den ersten 2 Beispielen haben wir Vererbung benutzt, um eine Klasse zu verwenden.
Das Stichwort hier ist "verwenden" und nicht "erweitern"!
Wir haben lediglich bestehende Funktionen der Klasse [JAPI]JFrame[/JAPI] verwendet, aber keine neue Funktionalität hinzugefügt.
Solange wir das nicht machen, besteht kein Grund von einer Klasse zu erben!

Verwenden != Erweitern
L-ectron-X hat gesagt.:
Wenn wir von einer Klasse erben, sprechen wir auch davon, sie zu spezialisieren.
Genauso, wie der Vater persönliche Eigenschaften an den Sohn vererbt, bekommt in der OOP eine erbende Klasse die Eigenschaften (Methoden und öffentliche Instanzvariablen) der Basisklasse (auch Superklasse) eingepflanzt. Die erbende Klasse muss aber für eine sinnvolle Anwendung der Vererbung weitere Eigenschaften erhalten, und/oder bestehende Eigenschaften spezialisieren.
Genauso wie der Sohn vom Vater die Fähigkeit erbte, besonders schnell die 60m zu laufen, kann der Sohn nun aber auch noch die 100m besonders schnell laufen. Eine Fähigkeit, zu der der Vater nicht im Stande ist.

Wie es im Englischen schön heißt: composition over inheritance.

Wir benutzen daher möglichst Felder anstatt Vererbung.

Felder können jederzeit ausgetauscht werden, unser Bücherregal könnte zur Programmlaufzeit die [JAPI]ArrayList[/JAPI] durch eine [JAPI]LinkedList[/JAPI] ersetzen, (oder wenn der typ des Feldes nur [JAPI]Collection[/JAPI]<String> wäre), sogar auf ein [JAPI]HashSet[/JAPI] umsteigen.
Benutzen wir dagegen Vererbung sind wir für alle Ewigkeit an die ArrayList gebunden und können zur Laufzeit nicht mehr davon weg.
Außerdem wird die Vererbung Teil der API unserer Software, ein anderer Programmierer wird unsere Komponente z.B. auch wie eine ArrayList verwenden, auch hier haben wir keine Möglichkeit etwas zu Ändern ohne anderen Code zu gefährden.
Halten wir dagegen solche Implementierungsdetails in privaten Feldern versteckt können wir zu jeder Zeit daran herumspielen, ohne das jemand etwas davon mitbekommen wird.

Ein Fall wo es tatsächlich Sinn macht von einer Grafikkomponente zu erben wäre dieser hier:
Java:
public class PaintingPanel extends JPanel{

	@Override
	public void paintComponent(Graphics g){
		super.paintComponent(g);
		g.drawLine(3, 3, 6, 6);
	}
	
}

Hier haben wir das JPanel tatsächlich erweitert, nämlich um eine hässliche kleine Linie die darauf gezeichnet wird :lol:

Zu bemerken ist hier das
Code:
@Override
und der super-Aufruf.
Es ist kein guter Stil, Methoden komplett zu überschreiben (z.B. würde das hier zu Zeichenfehlern im eigentlichen Panel führen), daher rufen wir zuerst die Methode der Superklasse auf.
Weiterhin zeigt uns das
Code:
@Override
an, dass wir eine Methode der Superklasse überschrieben haben (und der Compiler meckert uns an wenn, wir sie falsch geschrieben haben -> gängige Fehlerquelle vermieden).

Generell also:
Wenn wir eine Klasse verwenden wollen, legen wir ein Feld mit dem Typ der Klasse an und verwenden das Feld.

Wenn wir eine bestehende Klasse um neue Funktionalität erweitern wollen, dann erben wir von der Klasse.

composition over inheritance
 
Zuletzt bearbeitet von einem Moderator:

Oben