Bei der Generalisierung wird ausgenutzt, dass unterschiedliche Klassen teilweise gleiche Eigenschaften haben. Diese Eigenschaften müssen nur ein Mal beschrieben werden und können dann von allen erbenden Klassen genutzt werden. Ein EV3-Roboter und ein NXT-Roboter sind zwar sehr verschieden in Hinsicht auf die verbauten Komponenten, jedoch können diese beiden Elemente eindeutig als Roboter klassifiziert werden. Beide Roboter verfügen über vergleichbare Attribute (Eigenschaften) und Methoden, denn jeder EV3-Roboter und jeder NXT-Roboter hat beispielsweise eine Bezeichnung, eine Anzahl Motoren und eine bestimmte Geschwindigkeit, mit der die Motoren arbeiten sollen.
Fähigkeiten, die sowohl ein EV3 als auch ein NXT hat, können dann in
der sogenannten Oberklassse Roboter
implementiert werden. Ein Beispiel
hierfür ist die Fähigkeit, die Geschwindigkeit der Motoren zu verändern.
Wird diese Fähigkeit (Methode) in der Klasse Roboter beschrieben, gilt
sie gleichermaßen für den EV3 und den NXT und braucht dort nicht
erneut implementiert zu werden. Anstehende Änderungen müssen also nur
an einer Stelle vorgenommen werden. Aus Sicht einer Oberklasse
werden die Klassen, die in der Vererbungshierarchie weiter unten
liegen, immer konkreter. Dies wird Spezialisierung genannt. In
umgekehrter Richtung ist von Generalisierung die Rede (siehe
Abbildung 7.1).
Klassen können übersichtlich durch UML-Diagramme dargestellt werden. Die Unified Modeling Language (Vereinheitlichte Modellierungssprache), kurz UML, ist eine grafische Modellierungssprache zur Spezifikation, Konstruktion und Dokumentation von Software-Teilen und anderen Systemen. Sie wird von der Object Management Group (OMG) entwickelt und ist sowohl von ihr als auch von der ISO (ISO/IEC 19505 für Version 2.1.2) standardisiert [18]. Zusätzliche Informationen finden sich unter:
Abbildung 7.2 zeigt ein einfaches Beispiel für ein UML-Klassendiagramm: Es besteht aus drei übereinander liegenden Teilen. Im obersten Teil wird der Name der Klasse dargestellt. Zur besseren Übersicht wird er fett gedruckt. In der Mitte sind die Attribute dargestellt. Dies sind alle Variablen, die die Klasse bereitstellt. Im unteren Teil stehen die Methoden mit ihren Übergabeparametern und Rückgabewerten.
Wir wollen nun das Prinzip der Vererbung in der objektorientierten
Programmierung anhand eines Beispiels erläutern. Aus einer Oberklasse
Robot
werden zwei konkrete Unterklassen (Ev3
und Nxt
) abgeleitet (siehe
Abbildung 7.3). Die Unterklassen erben alle Methoden und Eigenschaften
der Oberklasse. Dafür wird in Java das Schlüsselwort extends
verwendet.
In der Oberklasse Robot existieren die Einträge name
für die Bezeichnung
des Roboters und motorSpeed
für die Geschwindigkeit, mit der
die verbauten Motoren arbeiten. Zusätzlich gibt es den Eintrag
motorCount
(Motorenanzahl), der mit dem Wert 0 initialisiert wird. Diese
Werte werden an Ev3 und Nxt vererbt, wo sie dann konkret belegt
werden.
Neben den größeren Motoren können Roboter, die auf dem EV3-System
basieren, auch kleinere Motoren verwenden. In unserem Beispiel soll der
EV3-Roboter mit so einem Motor eine Greiferklaue öffnen und schließen
können. Dafür wird die Eigenschaft smallMotorCount
(Anzahl kleiner
Motoren) und die Methoden openClaw
sowie closeClaw
in EV3
implementiert. Da diese Eigenschaften und Methoden nicht aus der
Oberklasse geerbt wurden, stehen sie auch nur der Klasse EV3
zur Verfügung. Genau so wird in der Klasse NXT
die Eigenschaft
currentArmAngle
sowie die Methode moveArmTo
implementiert, die der
NXT-Roboter benötigt, um einen Greifarm zu schwenken und die dann
auch nur der Klasse NXT
zur Verfügung stehen.
Die Kapselung von Informationen wird auch als »data hiding« bezeichnet. Damit werden die Eigenschaften innerhalb einer Klasse »versteckt«, um Zugriffe von außen zu verhindern. Den Variablen wird hierzu bei ihrer Deklaration eine Zugriffsberechtigung zugewiesen. Dies geschieht über eines der vier Schlüsselwörter (Modifier)
public
(öffentlich),
private
(privat) und
protected
(geschützt).Wird einer Variablen keine Zugriffsberechtigung zugewiesen, so erhält
diese den Zustand default
(siehe Tabelle 7.1).
public | protected | default | private | |
Selbe Klasse | Ja | Ja | Ja | Ja |
Selbes Package | Ja | Ja | Ja | Nein |
Unterklasse (extends) | Ja | Ja | Nein | Nein |
Überall (andere Klassen etc.) | Ja | Nein | Nein | Nein |
Es wird empfohlen, sich für jede Klasse zu überlegen, welche Variablen und Methoden von außen angesprochen werden sollen und welche nur innerhalb der Klasse oder des Package zur Verfügung stehen müssen.
Allgemein gilt: Was nicht für die Verwendung von außen, sondern nur für
die Programmierung im Inneren notwendig ist, sollte im Inneren
verborgen werden. Um ungültige Datenwerte in den Datenfeldern
(Variablen, Arrays etc.) zu vermeiden, ist es empfehlenswert, alle
Datenfelder als private
oder protected
zu definieren und eigene
von außen ansprechbare Methoden als public
für das Setzen und
Abfragen der Werte vorzusehen. Dies ist das Konzept der Getter- und
Setter-Methoden. Per Konvention sollen diese Methoden Namen der Form
setXxxx und getXxxx haben. Eine solche Methode haben wir bewusst
oder unbewusst schon in Abbildung 7.3 gesehen. Die Methode setSpeed
wird als public
deklariert, um von außen auf die private
-Variable
motorSpeed
zugreifen zu können. Im Folgenden Codebeispiel wird die
Oberklasse Robot
mit motorSpeed- Setter implementiert und um einen
motorSpeed-Getter erweitert.
Dieses Konzept bietet den großen Vorteil, dass die Klassen über wohl definierte Schnittstellen (hier Methoden) angesprochen werden können. Änderungen in der Variablenstruktur müssen nur in einer Klasse umgesetzt werden. Die andere Klasse greift ausschließlich auf die Schnittstellenmethoden zu.
Ein weiterer Vorteil ist, dass diese Methoden auch der Kontrolle dienen können. Es kann beispielsweise eine logische Prüfung der übergebenen Werte stattfinden. So kann überprüft werden, ob der Wert für die Reifenzahl positiv und damit gültig ist. Ein negativer Wert bei der Angabe der Reifenanzahl eines Autos könnte auf diese Weise unterbunden werden.
In der Setter-Methode wurde der sogenannte »this«-Pointer zum
Setzen der Variable verwendet. Dieser stellt eine Referenz auf die
Instanz dar, aus der die Methode aufgerufen wurde. Er wird an
dieser Stelle benötigt, da die Instanzvariable speed
sonst nicht
vom Übergabeparameter speed
zu unterscheiden wäre. Getter-
und Setter-Methoden können in dieser Form auch automatisch
von Eclipse durch Rechtsklick auf die gewünschte Variable und
Auswählen von Source → Generate Getters and Setters... erstellt
werden.
Das Wort Polymorphie entstammt der griechischen Sprache und bedeutet »Vielgestaltigkeit«. Die Polymorphie der objektorientierten Programmierung ist eine Eigenschaft, die in Zusammenhang mit Vererbung einhergeht. Eine Methode ist genau dann polymorph, wenn sie von verschiedenen Klassen unterschiedlich genutzt wird.
Gibt es in einer Klassenhierarchie mehrere Methoden auf unterschiedlichen Hierarchieebenen, wird erst zur Laufzeit festgelegt, welche der Methoden für ein gegebenes Objekt verwendet wird. Bei einer mehrstufigen Vererbung wird jene Methode verwendet, die direkt in der Objektklasse definiert ist, oder jene, die im Vererbungszweig am weitesten »unten« liegt. Variablen für Objekte sind in Java stets polymorph. Das bedeutet, dass eine einzelne Variable vom Typ der Oberklasse für verschiedene Objekte erbender Klassen in einem Programm verwendet werden kann. Wenn die Variable verwendet wird, um eine Methode aufzurufen, hängt es vom Objekt ab, auf das die Variable gegenwärtig verweist, welche Methode ausgeführt wird. Das folgende Codebeispiel soll die Polymorphie in Java erläutern.
Die Ausgabe dieses Programms lautet:
Die Methode moveForward()
unterliegt also offensichtlich der
Vielgestaltigkeit. Zweimaliges Aufrufen von der Variable anyRobot
aus
mit demselben Methodenaufruf führt zu unterschiedlichen Ausgaben. Dies
geschieht, da die Methode in den Unterklassen Ev3
und Nxt
jeweils
unterschiedlich überschrieben wurde.