Warum man nicht von JFrame/JDialog erben sollte

Aus Byte-Welt Wiki
Zur Navigation springenZur Suche springen

Dieser Beitrag wurde von Firephoenix erstellt

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

<code=java>public class Gui extends JFrame{

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

}</code=java>

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

Tatsächlich ist der Code genauso furchtbar wie das hier:

<code=java>public class Buchregal extends ArrayList<String>{

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

}</code=java>

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)

<code=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");
   }

}</code=java>

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

<code=java>public class Gui{

   private JFrame frame;
   
   public Gui(){
       frame = new JFrame("Toller titel");
       frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
       frame.add(new JButton("Out Of Order"));
       frame.pack();
       frame.setVisible(true);
   }

}</code=java>

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 JFrame verwendet, aber keine neue Funktionalität hinzugefügt. Solange wir das nicht machen, besteht kein Grund von einer Klasse zu erben!

Verwenden != Erweitern

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 ArrayList durch eine LinkedList ersetzen, (oder wenn der typ des Feldes nur Collection <String> wäre), sogar auf ein HashSet 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:

<code=java>public class PaintingPanel extends JPanel{

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

} </code=java>

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

Zu bemerken ist hier das @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 @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


Fragen

Das Thema wurde nicht ausreichend behandelt? Du hast Fragen dazu und brauchst weitere Informationen? Lass Dir von uns helfen!

Wir helfen dir gerne!


Dir hat dieser Artikel gefallen? Oder Du hast Fehler entdeckt und möchtest zur Berichtigung beitragen? Prima! Schreibe einen Kommentar!

Du musst angemeldet sein, um einen Kommentar abzugeben.


--Firephoenix 19:18, 28. Aug 2013 (CET)