In der Folge 5.1 haben wir Java-Anwendungen kennengelernt. Eine KI hat uns das Grundgerüst für solche Anwendungen erstellt, und wir haben anschließend nur die wichtige Methode paintComponent() verändert und dort Kreise, Rechtecke und Linien gezeichnet. Dabei haben wir außerdem for- und while-Schleifen eingesetzt, um dieses wichtige Thema zu vertiefen.
In dieser Folge lernen wir nun ein weiteres wichtiges Konzept der Objektorientierten Programmierung (OOP) kennen, nämlich die Vererbung.
Lernziele
Wenn Sie diese Seite und die Folgeseiten durchgearbeitet haben, sollten Sie
- erklären können, warum man in der objektorientierten Programmierung Vererbung einsetzt.
- den Unterschied zwischen einer Oberklasse und einer Unterklasse erklären können.
- eine einfache Oberklasse entwickeln können, welche gemeinsame Attribute und Methoden mehrerer Klassen zusammenfasst.
- mit dem Schlüsselwort extends eine Unterklasse von einer Oberklasse ableiten können.
- den Konstruktor einer Oberklasse mit super(...) aufrufen können.
- erklären können, welche Vorteile Vererbung gegenüber dem Kopieren von Quelltext bietet.
- den Unterschied zwischen einer konkreten und einer abstrakten Klasse kennen.
- erklären können, wozu abstrakte Methoden dienen.
- Methoden einer Oberklasse in einer Unterklasse überschreiben können.
- das Grundprinzip der Polymorphie anhand einfacher Beispiele erläutern können.
Implementierung der Klasse Rectangle
Übung 5.1-8
Erstellen Sie nun eine Klasse Rectangle, mit der Rechtecke und Quadrate erzeugt werden können. Auch hier soll die Randfarbe und die Randstärke durch Setter-Methoden festgelegt werden.
In der Aufgabe 8 aus der Folge 5.1 sollten Sie eine Klasse Rectangle implementieren, mit der man farbige Rechtecke zeichnen kann. Vermutlich sind Sie dabei ähnlich vorgegangen wie ich selbst, als ich die Musterlösung erstellt habe.
Typisches Vorgehen
- Man kopiert den Quelltext der Klasse Circle in die Zwischenablage
- Man erstellt eine neue Klasse Rectangle
- Man kopiert den Inhalt der Zwischenablage in das leere Editorfenster
- Man verändert den Circle-Quelltext so lange, bis daraus eine funktionierende Klasse Rectangle entstanden ist.
Bei der Erstellung der Klasse Rectangle fällt auf, dass am ursprünglichen Quelltext der Klasse Circle gar nicht so viele Änderungen vorgenommen werden müssen.
Instanzvariablen
- Die Instanzvariablen xPos und yPos können wir unverändert übernehmen
- Die Instanzvariable radius muss durch die neuen Instanzvariablen breite und hoehe ersetzt werden.
Konstruktor
Der Konstruktor der Klasse Circle muss an einigen Stellen angepasst werden. Für Rectangle-Objekte benötigen wir jetzt vier Parameter, zwei für die Position und zwei für Breite und Höhe.
Circle-Konstruktor
public Circle(int xPos, int yPos, int radius)
{
this.xPos = xPos;
this.yPos = yPos;
this.radius = radius;
color = Color.RED;
randfarbe = Color.BLACK;
randstaerke = 1;
}
Rectangle-Konstruktor
public Rectangle(int xPos, int yPos, int breite, int hoehe) // neu
{
this.xPos = xPos;
this.yPos = yPos;
this.breite = breite; // neu
this.hoehe = hoehe; // neu
color = Color.RED;
randfarbe = Color.BLACK;
randstaerke = 1;
}
Abgesehen von diesen wenigen Änderungen sind die beiden Konstruktoren nahezu identisch.
Methoden
Circle-Methoden
public void setColor(Color color)
{
this.color = color;
}
public void setRandfarbe(Color randfarbe)
{
this.randfarbe = randfarbe;
}
public void setRandstaerke(int randstaerke)
{
if (randstaerke < 1)
randstaerke = 1;
this.randstaerke = randstaerke;
}
Rectangle-Methoden
public void setColor(Color color)
{
this.color = color;
}
public void setRandfarbe(Color randfarbe)
{
this.randfarbe = randfarbe;
}
public void setRandstaerke(int randstaerke)
{
if (randstaerke < 1)
randstaerke = 1;
this.randstaerke = randstaerke;
}
Die drei Methoden sind absolut identisch, hier musste überhaupt nicht geändert werden.
Circle.paint()
public void paint(Graphics g)
{
g.setColor(randfarbe);
g.fillOval(xPos-radius,yPos-radius,radius*2,radius*2);
g.setColor(color);
g.fillOval(xPos-radius+randstaerke,
yPos-radius+randstaerke,
radius*2-2*randstaerke,
radius*2-2*randstaerke);
}
Rectangle.paint()
public void paint(Graphics g)
{
g.setColor(randfarbe);
g.fillRect(xPos,yPos,breite,hoehe);
g.setColor(color);
g.fillRect(xPos+randstaerke,yPos+randstaerke,
breite-2*randstaerke,
hoehe-2*randstaerke);
}
Auch diese beiden Methoden besitzen denselben grundsätzlichen Aufbau. Unterschiede gibt es jedoch bei den Koordinaten und den Zeichenbefehlen. Beim Kreis geben die Koordinaten den Mittelpunkt an, beim Rechteck dagegen die linke obere Ecke. Außerdem wird beim Kreis der Radius verwendet, während beim Rechteck Breite und Höhe angegeben werden.
Beurteilung dieses Verfahrens
Der folgende Abschnitt wurde von einer KI generiert (ChatGPT) und dann von mir kontrolliert und etwas überarbeitet.
Die beschriebene Vorgehensweise funktioniert durchaus und wird von vielen Programmierern zunächst genau so angewendet. Für kleine Programme ist das oft sogar ausreichend.
Je größer ein Projekt jedoch wird, desto deutlicher zeigen sich aber die Nachteile dieses Verfahrens.
Die Methoden
- setColor()
- setRandfarbe()
- setRandstaerke()
sind in beiden Klassen vollständig identisch. Wird später in einer dieser Methoden ein Fehler entdeckt oder soll ihr Verhalten geändert werden, muss dieselbe Änderung in allen betroffenen Klassen vorgenommen werden. Vergisst man dabei eine Klasse, entstehen leicht Fehler, die nur schwer zu finden sind.
Außerdem enthält der Konstruktor beider Klassen denselben Quelltext zur Initialisierung der Farben und der Randstärke. Auch dieser Code kommt mehrfach vor und muss gegebenenfalls mehrfach geändert werden.
Ein weiteres Problem besteht darin, dass mit jeder neuen Figurenklasse immer mehr doppelter Quelltext entsteht. Fügt man beispielsweise noch die Klassen Triangle, Ellipse, Polygon oder Line hinzu, werden viele Methoden immer wieder neu kopiert.
In der Softwareentwicklung gilt jedoch ein wichtiger Grundsatz:
Vermeide Redundanzen im Quelltext!
Das heißt: Quelltext sollte nach Möglichkeit nicht doppelt oder mehrfach vorhanden sein. Doppelt vorhandener Code macht Programme länger, unübersichtlicher und schwieriger zu warten.
Genau für solche Situationen stellt die objektorientierte Programmierung das Konzept der Vererbung bereit. Gemeinsamkeiten werden nur noch ein einziges Mal programmiert und anschließend von anderen Klassen übernommen.
Damit sind Sie jetzt hoffentlich stark motiviert, das Konzept der Vererbung kennenzulernen. Und damit wären wir dann schon bei der nächsten Folge.