7  Konzepte der OOP

7.1  Generalisierung

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.

pict

Abbildung 7.1.: Spezialisierung und Generalisierung

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).

7.1.1  UML

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.

pict

Abbildung 7.2.: UML-Klassendiagramm

7.2  Vererbung

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.

class EV3 extends Robot

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.

pict

Abbildung 7.3.: UML-Diagramm: Vererbung

pict
In Java ist es nicht möglich, dass eine Klasse von mehreren Klassen erbt.
Dieses Vererbungsprinzip erlaubt es, dass Vererbungen beliebig geschachtelt werden können. Die abgeleitete Klasse erbt die Eigenschaften der Oberklasse, die wiederum die Eigenschaften ihrer Oberklasse erbt usw.

7.3  Kapselung

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)

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

Tabelle 7.1.: Zugriffsrechte
pict
Java-Programme bestehen aus Ansammlungen von vielen einzelnen Dateien, die innerhalb der objektorientierten Programmierung jeweils eine Klasse repräsentieren. Um dieser Klassensammlung eine Struktur geben zu können und somit die Les- und Benutzbarkeit für Menschen zu verbessern, wurden Java Packages eingeführt. [10]
In UML-Notation wird dies folgendermaßen umgesetzt:
+ public (öffentlich)
– private (privat)
# protected (geschützt)
~ (Paketsichtbar)

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.


Programm 7.1: Implementierung von Getter- und Setter- Methoden
 
1public class Robot{ 
2 
3    private String name; 
4    private int motorCount = 0; 
5    private int speed = 0; 
6 
7    /∗∗ 
8     Motorspeedsetter. Sets speed. 
9    / 
10    public void setSpeed(int speed) { 
11        this.speed = speed; 
12    } 
13    /∗∗ 
14     Motorspeedgetter. Returns speed. 
15    / 
16    public int getSpeed() { 
17        return speed; 
18    } 
19 
20    public void moveForward() { 
21        //add implementation of moveForward method here 
22    } 
23}

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.

7.3.1  this-Pointer

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.

7.4  Polymorphie

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.


Programm 7.2: Polymorphie
 
1import lejos.hardware.Button; 
2import lejos.hardware.lcd.LCD; 
3 
4class Robot{ 
5    private String name; 
6    private int motorCount = 0; 
7    private int speed = 0; 
8 
9    public void setSpeed(int speed) {this.speed = speed;} 
10    public int getSpeed() {return speed;} 
11    public void moveForward() { 
12        LCD.drawString("The ROBOT is moving", 0, 0); 
13    } 
14} 
15 
16class EV3 extends Robot{ 
17    private int motorCount = 2; 
18    private int smallMotorCount = 1; 
19 
20    public void closeClaw() {//add implementation of closeClaw method here} 
21    public void openClaw() {//add implementation of openClaw method here} 
22    public void moveForward() { 
23        LCD.drawString("The EV3 is moving", 0, 1); 
24    } 
25} 
26 
27class NXT extends Robot{ 
28    private int motorCount = 3; 
29    private int currentArmAngle = 0; 
30 
31    public void moveArmTo(int angle) {//add implementation of moveArmTo method here} 
32 
33    public void moveForward() { 
34        LCD.drawString("The NXT is moving", 0, 2); 
35    } 
36} 
37 
38public class Polymorphie { 
39    public static void main(String[] args) { 
40        Robot anyRobot; 
41        anyRobot = new EV3(); 
42        anyRobot.moveForward(); 
43 
44        anyRobot = new NXT(); 
45        anyRobot.moveForward(); 
46 
47        Button.ENTER.waitForPress(); 
48    } 
49}

Die Ausgabe dieses Programms lautet:

pict

Abbildung 7.4.: Ausgabe auf dem Display des EV3

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.