betreutProg4_musterloesung

Werbung
WS 2011/2012
SS 2011
Fakultät für Angewandte Informatik
Lehrprofessur
für Informatik
Programmierung
verteilter Systeme
15.05.2011
21.11.2011
Prof.
LorenzBauer
Prof.Dr.
Dr.Robert
Bernhard
Betreutes Programmieren Vorlesung Informatik II, Blatt 4 – Musterlösung
Programmieraufwand für geübte Programmierer:
Programmieraufwand für ungeübte Programmierer:
Musterlösung: ca. 88 Codezeilen∗
max. 45 min
1 – 1,5 h
ToDo-Manager
Der ToDoManager hilft Ihnen, Aufgaben ihrer Wichtigkeit nach zu sortieren. Sie geben Aufgaben mit ihrer
Beschreibung und einer Priorität in das System ein, und der Manager wählt eine geeignete Sortierung für Sie aus,
die er Ihnen dann präsentiert.
Aufgabenstellung
Setzen Sie das UML-Klassendiagramm aus Abbildung 1 in Java-Code um und berücksichtigen Sie dabei die
weiteren Beschreibungen und Hinweise nach dem Diagramm.
+ Aufgabe
- beschreibung: String {nicht-leer}
- prioritaet: Integer {≥ 0, ≤ 5}
«interface» Iterable<Aufgabe>
+ iterator(): Iterator<Aufgabe>
+ Aufgabe(beschreibung: String,
prioritaet: Integer)
+ getBeschreibung(): String
+ getPrioritaet(): Integer
+ equals(o: Object): Boolean
+ Aufgaben
- unique: Aufgaben = null
0..* - aufgaben
1..1 +
+
+
+
+ Object
Aufgaben()
instance(): Aufgaben
addAufgabe(aufgabe: Aufgabe)
popAufgabe(): Aufgabe
iterator(): Iterator<Aufgabe>
Abbildung 1: UML-Klassendiagramm der Datenkomponenten
Überlegen Sie, wie Sie die Einschränkungen der Attribute von Aufgabe umsetzen können.
Die Methode equals() soll genau dann true zurückliefern, wenn sowohl die Beschreibung als auch die
Priorität von zwei Aufgaben gleich sind.
Die Methode addAufgabe() soll eine neue Aufgabe nur dann zum System hinzufügen, wenn nicht bereits
eine identische Aufgabe im System vorhanden ist.
∗ Nicht
gezählt: Leerzeilen, Kommentare, imports. In eigenen Zeilen: { und } (ca. 30x), jede Anweisung, außer bei Deklarationen gleichen Typs (z. B. int a, b, c = 0, . . . ) oder bei einer einzelnen Anweisung nach einem if oder einem else. Nicht
benutzt: ?:-Operator.
1
Die Methode popAufgabe() gibt die dringlichste Aufgabe, die im System enthalten ist, zurück und löscht sie
aus dem Container.
Wie Sie sehen, soll die Klasse Aufgaben die Schnittstelle Iterable implementieren. Weiter erkennen Sie
das Singleton-Muster, in dem Aufgaben zu erstellen ist.
Erstellen Sie eine main()-Methode, um Ihre Implementierung zu testen.
Hinweise
Sollten Sie noch grundlegendere Hinweise als die in diesem Abschnitt gegebenen benötigen, dann werfen Sie
einen Blick in den Anhang ab Seite 6. Dort sind weitergehende Erklärungen zusammengestellt.
Beginnen Sie mit der Klasse Aufgabe. Eine Möglichkeit die Einschränkungen zu berücksichtigen ist, im
Konstruktor die übergebenen Parameter abzufragen und ggf. die Attribute passend zu initialiseren.1 Wenn Sie
in der Methode equals() die beiden Beschreibungen miteinander vergleichen, benutzen Sie die Methode
equalsIgnoreCase() der Klasse String. Auf diese Weise wird nicht zwischen Groß- und Kleinschreibung unterschieden.
Lösung
Die Klasse Aufgabe wird wie für Java üblich in einer eigenen Datei implementiert; sie heißt Aufgabe.java.
Im Konstruktor wird der Hinweis aus Fußnote 1 auch auf das Attribut beschreibung ausgedehnt. Die beiden
getter-Methoden sind Standard; ebenso die equals()-Methode.
public class Aufgabe
{
private String beschreibung;
private int prioritaet;
public Aufgabe(String
{
this.beschreibung =
if (beschreibung ==
this.beschreibung
beschreibung, int prioritaet)
beschreibung;
null || beschreibung.length() == 0)
= "(keine Beschreibung)";
this.prioritaet = prioritaet;
if (prioritaet > 5) this.prioritaet = 5;
if (prioritaet < 0) this.prioritaet = 0;
}
public String getBeschreibung()
{
return beschreibung;
}
public int getPrioritaet()
{
return prioritaet;
}
public boolean equals(Object o)
{
if (o instanceof Aufgabe &&
prioritaet == ((Aufgabe) o).prioritaet &&
1 Wird
bspw. eine Priorität größer als 5 übergeben, wird das Attribut prioritaet trotzdem auf den Wert 5 gesetzt.
2
beschreibung.equalsIgnoreCase(((Aufgabe) o).beschreibung))
return true;
return false;
}
}
Kompiliert wird die Klasse mit dem Befehl javac Aufgabe.java.
Bei der Klasse Aufgaben schreiben Sie zunächst das Singleton-Muster, das Sie bereits aus der Vorlesung kennen.
Lösung
Die Klasse Aufgaben wird in einer zweiten Datei Aufgaben.java implementiert.
Zunächst wird nur der Rumpf geschrieben, der das Singleton-Muster implementiert, und Iterator importiert,
da die Schnittstelle Iterable implementiert werden soll. Da nur Objekte des gleichen Typs verwaltet werden,
wird die Schnittstelle noch parametrisiert und es wird sogar Iterable<Aufgabe> implementiert.
Da noch unklar ist, welche weiteren Klassen oder Schnittstellen benötigt werden, wird auf jeden Fall mal Platz
für zusätzliche Importe vorgemerkt.
import java.util.Iterator;
hzusätzliche Importei
public class Aufgaben implements Iterable<Aufgabe>
{
private static Aufgaben unique = null;
private Aufgaben()
{
hRumpf des privaten Konstruktors Aufgaben()i
}
public static Aufgaben instance()
{
if (unique == null) unique = new Aufgaben();
return unique;
}
hAttribut aufgabeni
hMethode addAufgabe()i
hMethode popAufgabe()i
hMethode iterator()i
}
Die Methoden addAufgabe(), popAufgabe() und iterator() werden später vorgestellt; ebenso die
Wahl für das Attribut aufgaben.
Da noch unklar ist, ob der Konstruktor etwas zu tun hat oder nicht, wird seine Füllung ebenfalls verschoben.
3
Überlegen Sie nun, welchen Container von Java Sie benutzen könnten, um die einzelnen Aufgaben zu verwalten. Recherchieren Sie in der Dokumentation nach Klassen, die eine Liste oder ein Warteschlange repräsentieren,
und benutzen Sie weitestgehend deren Methoden.
Lösung
Java bietet eine Klasse namens PriorityQueue an, die sich schon selbst darum kümmert, ihre Elemente in
der richtigen Reihenfolge vorzuhalten. Es wird also ein weiterer Import benötigt.
import java.util.PriorityQueue;
Nun kann auch das fehlende Attribut deklariert werden, wobei gleich die passende Parametrisierung vorgenommen wird.
PriorityQueue<Aufgabe> aufgaben;
Die Dokumentation von PriorityQueue spricht davon, dass nur Elemente verwaltet werden können, die
miteinander vergleichbar sind. Dazu wird eine eigene Klasse herangezogen, deren einzige Aufgabe es ist, zwei
Aufgaben zu vergleichen. Sie wird in einer eigenen Datei namens AufgComp.java implementiert und verwirklicht die Schnittstelle Comparator, wie in der Dokumentation gefordert; auch hier wieder mit der passenden Parametrisierung.
import java.util.Comparator;
public class AufgComp implements Comparator<Aufgabe>
{
public int compare(Aufgabe a1, Aufgabe a2)
{
if (a1.getPrioritaet() == a2.getPrioritaet())
return a1.getBeschreibung().compareToIgnoreCase(a2.getBeschreibung());
return a2.getPrioritaet() - a1.getPrioritaet();
}
}
Mit dieser zusätzlichen Klasse (kompiliert durch den Aufruf javac AufgComp.java), kann nun im Rumpf
des Konstruktors das Attribut aufgaben initialisert werden. Da nur ein Konstruktor existiert, der neben einem
Vergleichsobjekt auch eine anfängliche Größe erwartet, wird einfach 10 als Anfangswert mit übergeben – mehr
Aufgaben will man ja auch nicht erledigen müssen. Der Konstruktorrumpf besteht also nur aus einer Zeile.
aufgaben = new PriorityQueue<Aufgabe>(10, new AufgComp());
Die Methode addAufgabe() kann nun wie folgt geschrieben werden, wobei der Aufruf von contains() auf
der korrekten Implementierung der equals()-Methode fußt sowie die passende Einsortierung auf der korrekten
Implementierung der compare()-Methode.
public void addAufgabe(Aufgabe aufgabe)
{
if (!aufgaben.contains(aufgabe))
aufgaben.offer(aufgabe);
}
Die Methode popAufgabe() kann ebenfalls sehr einfach gestaltet werden.
public Aufgabe popAufgabe()
{
return aufgaben.poll();
}
4
Die Methode iterator() verkommt ebenfalls zu einem Einzeiler. Allerdings geht aus der Dokumentation
hervor, dass der Iterator die Liste in keiner speziellen Ordnung durchläuft. Die Aufgabe, auf die der Iterator als
erstes zeigt, ist also nicht zwangsläufig die Aufgabe, die man über popAufgabe() erhalten würde. Da jedoch in
den Anforderungen nicht genau spezifiziert ist, welche Reihenfolge hier maßgebend sein soll, wurde die einfache
Variante gewählt und die Reihenfolge nicht berücksichtigt bzw. die Nicht-Reihenfolge von PriorityQueue
durchgereicht.
public Iterator<Aufgabe> iterator()
{
return aufgaben.iterator();
}
Nun wird diese Klasse analog mittels javac Aufgaben.java kompiliert.
Sollte Ihnen die Implementierung der eigentlichen Verwaltungsoperationen nicht in der angegebenen Zeit gelingen, ist das kein Beinbruch und nicht das Kriterium für die Vergabe des Punktes! Das wesentliche Augenmerk
sollten Sie auf die Umsetzung des UML-Diagramms in Code legen, wozu allerdings das Singleton-Muster, die
Einschränkungen sowie die Schnittstelle zählen. Lassen Sie in diesem Fall die Methoden addAufgabe() und
popAufgabe() leer.
Schreiben Sie abschließend eine eigene Klasse, die die main()-Methode enthält und in der Sie verschiedene
Aufgaben erzeugen und in den Container einfügen. Testen Sie, ob die Reihenfolge richtig ist, ob Aufgaben
nicht doppelt eingefügt werden und ähnliches.
Lösung
Das vorgeschlagene Vorgehen wird in der Datei Main.java umgesetzt.
public class Main
{
public static void main(String[] args)
{
Aufgaben.instance().addAufgabe(new Aufgabe("", 2));
System.out.println(Aufgaben.instance().popAufgabe().getBeschreibung());
Aufgaben.instance().addAufgabe(new Aufgabe("", 5));
Aufgaben.instance().addAufgabe(new Aufgabe("", 7));
System.out.println(Aufgaben.instance().popAufgabe().getBeschreibung());
if (Aufgaben.instance().popAufgabe() != null)
System.out.println("Mist!");
Aufgaben.instance().addAufgabe(new
Aufgaben.instance().addAufgabe(new
Aufgaben.instance().addAufgabe(new
Aufgaben.instance().addAufgabe(new
Aufgabe("Einkaufen", 4));
Aufgabe("Essen", 2));
Aufgabe("Kochen", 3));
Aufgabe("Einkaufen", 3));
for (Aufgabe a: Aufgaben.instance())
System.out.println(a.getBeschreibung() + " " + a.getPrioritaet());
for (Aufgabe a = Aufgaben.instance().popAufgabe(); a != null;
a = Aufgaben.instance().popAufgabe())
System.out.println(a.getBeschreibung() + " " + a.getPrioritaet());
}
}
Zuletzt wird auch diese Klasse kompiliert (javac Main.java) und anschließend das Programm mit dem
Aufruf java Main getestet.
5
Weitergehende Erklärungen
B.1 Konstruktoren . . . . . . . . . . .
B.2 Singleton-Muster . . . . . . . . .
B.3 Vererbung . . . . . . . . . . . . .
B.3.1 Die Methode equals()
B.3.2 Polymorphismus . . . . .
B.4 Schnittstellen . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
6
7
9
9
10
B.1 Konstruktoren
Eine Erklärung zu Konstruktoren findet sich bereits auf dem letzten Blatt zum Betreuten Programmieren. Das
Blatt finden Sie auf der Lehrstuhlseite zum Download.
B.2 Singleton-Muster
Grundsätzlich handelt es sich bei Mustern um allgemeine Lösungen zu wiederkehrenden Problemen. Diese Lösungen sind meistens in Form von Objekten, deren Methoden und Beziehungen zwischen den Objekten gegeben,
falls es sich um mehrere handelt.
Das Singleton-Muster löst das Problem, wenn man sicherstellen möchte, dass von einer Klasse höchstens ein
Objekt erzeugt werden kann. Aus der Vorlesung wissen Sie, dass dies bei Containerklassen sinnvoll ist. Ein
Container kann z. B. ein Lager repräsentieren und zur Verwaltung von Geräten eingesetzt werden. Möchte man
nun sichergehen, dass nur ein Lager im System existiert, erstellt man Lager im Singleton-Muster.
public class Lager
{
private static Lager unique = null;
public static Lager instance()
{
if (unique == null) unique = new Lager();
+ Geraet
...
...
- geraete 0..*
1..1
return unique;
+ Lager
}
- unique: Lager = null
...
- Lager()
+ instance(): Lager
...
private Lager()
{
hRumpf des privaten Konstruktorsi
}
Abbildung 2: Singleton in UML
hsonstige Attribute und Methoden für Lageri
}
Das statische Attribut unique nimmt das einzige Objekt der Klasse auf. Von außerhalb der Klasse kann kein
Objekt erzeugt werden, da der Konstruktor private ist. Da die Methode instance() ebenfalls statisch ist,
kann Sie ohne ein Objekt, einfach über Lager.instance(), aufgerufen werden. Auf diese Weise bekommt
man Zugriff auf das einzige Objekt der Klasse. Alle weiteren Attribute und Methoden werden wie gehabt in die
Klasse eingefügt.
In UML erkennt man das Singleton-Muster, neben den beiden statischen Komponenten und dem privaten Konstruktor, vor allem an der Multiplizität 1..1, die angibt, dass ein Objekt genau einmal existiert. Dies kann man
in Abbildung 2 sehen. Dort ist zusätzlich zu sehen, dass Lager ein Container ist, der beliebig viele Objekte von
Geraet verwaltet. Die genauen Verwaltungsoperationen wurden hier der Einfachheit halber weggelassen.
6
B.3 Vererbung
Dabei handelt es sich um einen integralen Bestandteil der objektorientierten Programmierung (OOP). Die Idee
dahinter ist, dass man bereits bestehende Klassen erweitern kann. Aus diesem Grund wird Vererbung in Java mit
dem Schlüsselwort extends eingeleitet. Eine abgeleitete Klasse – oder Kindklasse – übernimmt alle Attribute
und Methoden der ursprünglichen Klasse – der Elternklasse – kann sie aber erweitern und ergänzen.
Eine Vererbung drückt eine ist-ein-Beziehung aus. Wenn es mehrere gleichartige Klassen gibt, die alle eine
gemeinsame Basis besitzen, so ist dies ein guter Kandidat für eine Vererbungshierarchie.
Bspw. sind Batman und Superman beides Superhelden. Ein Superheld hat einen Tarnnamen, unter dem er ein normales Leben führt und den er nicht einfach preisgibt, und einen Heldennamen, unter dem er in der Öffentlichkeit
auftritt. In Abbildung 3 ist die gemeinsame Basisklasse in UML dargestellt.
public class Superheld
{
private String heldenname;
private String tarnname;
protected String specialGimmick;
public Superheld(String tarnname, String heldenname)
{
this.tarnname = tarnname;
this.heldenname = heldenname;
}
+ Superheld
- heldenname: String
- tarnname: String
# specialGimmick: String
public String getHeldenname()
{
return heldenname;
}
+ Superheld(heldenname: String,
tarnname: String)
+ getHeldenname(): String
# getTarnname(): String
protected String getTarnname()
{
return tarnname;
}
Abbildung 3: UML Superheld
}
Der Code ist die direkte Entsprechung des Diagramms in Java. Wie nach dem Geheimnisprinzip üblich, sind
die Namen als private Attribute angelegt. Den Heldennamen darf jeder erfahren, weswegen der entsprechende
Getter öffentlich zugänglich ist. Der Getter für den Tarnnamen ist mit dem Schlüsselwort protected versehen
(in UML mit #), was bedeutet, dass er nur von Kindklassen aus aufrufbar ist. Wenn Batman also ein Superheld ist,
dann hat er über den Getter Zugriff auf seinen Tarnnamen, aber sonst niemand. Er sollte sich dann eine Methode
zulegen, die über eine Art Zugriffskontrolle seinen Tarnnamen an Eingeweihte herausrückt, um seine Identität
nicht auffliegen zu lassen.
Da die Namen der Parameter im Konstruktor den Namen der Attribute gleichen, sind die Attribute im Konstruktor
verdeckt. Sie müssen über das Schlüsselwort this angesprochen werden, das eine Referenz auf das aktuelle
Objekt darstellt.
7
Jeder Superheld hat auch ein besonderes Gimmick. In diesem Fall wird das Geheimnisprinzip umgangen, da ein
Superheld nicht erst jedesmal seine Eltern bitten kann, wenn er sein Gimmick einsetzen will. Das würde einfach
zu lange dauern. Das Attribut wird deswegen mit protected versehen.
public class Batman extends Superheld
{
hzusätzlicher Batkrami
+ Superheld
...
...
public Batman()
{
super("Bruce Wayne", "Batman");
haktiviere batmäßige Coolness und anderen Batkrami
}
hcoole Bataktioneni
+ Batman
...
+ Batman() ...
Abbildung 4: UML Batman
}
Abbildung 4 kann man entnehmen, dass Vererbung in UML durch eine durchgezogene Linie mit einer leeren
Pfeilspitze dargestellt wird. In UML spricht man von Generalisierung und nicht von Vererbung, da man die
gegenteilige Richtung betrachtet. Vererbung ist gewissermaßen eine Spezialisierung.
Wenn eine Kindklasse eine Methode der Elternklasse überschreibt, indem sie eine Methode gleichen Namens
und gleicher Signatur – bei Konstruktoren ist hier nur die Parameterliste von Bedeutung – anlegt, dann kann man
in Java von innerhalb dieser Methode die Methode der Elternklasse über das Schlüsselwort super aufrufen. Innerhalb eines Konstruktors kann jeder Konstruktor der Elternklasse mit der passenden Parameterliste aufgerufen
werden. Auf diese Weise kann bestehende Funktionalität erweitert werden.
Batman hat mehrere Gimmicks und überschreibt deshalb das Attribut specialGimmick aus der SuperheldKlasse und macht es zu einem Feld.
private String[] specialGimmick;
Das Attribut der Elternklasse ist nun verdeckt kann aber über super angesprochen werden, sodass die batmäßige
Coolness im Konstruktor auf diese Weise gesetzt werden kann.
super.specialGimmick = "batmäßige Coolness";
Die anderen Gimmicks werden direkt über das Feld angesprochen. Mehr als zehn Gimmicks schafft der Batgürtel
allerdings auch nicht.
specialGimmick = new String[10];
Eine Kindklasse kann also auch Attribute einer Elternklasse überschreiben und deren Typ verändern. Beide
Attribute stehen danach zur Verfügung.
In Java gibt es keine Mehrfachvererbung. Jede Klasse hat also genau eine Elternklasse. Für diese Regel gibt es
eine Ausnahme: Object ist szsg. die Adamklasse und wurde direkt in der Sonne geboren. Alle Klassen sind
direkt oder indirekt über eine Vererbungshierarchie Kinder von Object.
Schreibt man sich also in Java eine neue Klasse und lässt die extends-Klausel weg, dann wird sie automatisch
durch extends Object aufgefüllt.
8
B.3.1 Die Methode equals()
Diese Methode wird in Java eingesetzt, um herauszufinden, ob zwei Objekte eigentlich das gleiche Objekt
sind. Die Methode besitzen alle Objekte in Java, da jede Klasse automatisch von der Klasse Object erbt und
equals() dort definiert ist.
Zunächst verwundert der Parameter vom Typ Object. Auf diese Weise wird die Universalität der Methode
sichergestellt. Da jedes Objekt in Java auch einen Anteil vom Typ Object besitzt, können so Äpfel mit Birnen
verglichen werden.
Um auf Attribute und Methoden zuzugreifen, die eine bestimmte Klasse besitzt, muss man den Parameter geeignet casten. Dies ist allerdings nur zulässig, falls das Objekt tatsächlich auch vom Zieltyp ist. Um herauszufinden,
welcher Typ wirklich hinter einem Parameter steckt, gibt es den instanceof-Operator. Er wird zusammen mit
einem Objekt und einem Klassennamen aufgerufen und gibt ein boolesches Ergebnis2 zurück.
So wird eine equals()-Methode fast immer nach dem gleichen Schema ablaufen. Zuerst wird auf Klassenzugehörigkeit getestet, dann wird ggf. ein Typecast auf die entsprechende Klasse vorgenommen und this mit dem
gecasteten Objekt verglichen. Der Vergleich wird üblicherweise auf den Attributen der beiden Objekte durchgeführt.
public boolean equals(Object o)
{
if (o instanceof Klasse)
{
Klasse tmp = (Klasse) o;
hVergleiche Attribute von this und tmpi
}
return false;
}
B.3.2 Polymorphismus
Hierbei handelt es sich um einen weiteren integralen Bestandteil von OOP, der mit der Vererbung zusammenhängt
und eine große Stärke dieses Programmierparadigmas darstellt.
Polymorphismus beschreibt die Vielgestaltigkeit von Objektreferenzen. Da ein Objekt einer Kinderklasse immer
auch ein Objekt einer seiner Elternklassen ist, kann es auch einer Variablen solchen Typs zugewiesen werden.
Klasse klasse = new Klasse();
Object o = klasse;
Ein weiteres Objekt wird im Folgenden benötigt.
Klasse anderes = new Klasse();
Nun kommt die Magie. Der folgende Aufrauf ruft ganz klar die Methode equals() der Klasse Klasse auf.
klasse.equals(anderes);
Aber welche Methode wird in dieser Zeile aufgerufen?
o.equals(anderes);
2 Also
true oder false.
9
Dieselbe Methode wie im vorherigen Aufruf, denn das Objekt, das hinter der Referenz steckt, weiß, dass es ein
Objekt der Klasse Klasse ist.
Solche Methoden werden virtuelle Methoden genannt, da es nur vom Objekt selbst und nicht der Trägervariablen
abhängt, welche Methode – also wo in der Vererbungshierarchie – aufgerufen wird. In Java sind Methoden immer
virtuell. Das bedeutet, dass in Java grundsätzlich immer die „jüngste“ Methode ausgeführt wird, die existiert.
B.4 Schnittstellen
Eine Schnittstelle wird in UML durch «interface», wie in Abbildung 5 gezeigt, gekennzeichnet. Eine
Schnittstelle entspricht dem Konzept der Vorgabe von Methoden. Wenn eine Klasse eine bestimmte Schnittstelle implementiert, dann muss sie ganz bestimmte Methoden zur Verfügung stellen; eben diejenigen, die die
Schnittstelle vorgibt.
In Java ähnelt eine Schnittstelle einer Klasse. Dabei wird nur das Schlüsselwort class gegen das Schlüsselwort
interface ausgetauscht. Weiter dürfen keine Attribute mehr enthalten sein und die Methoden werden nur
noch deklariert, enthalten also keine Rümpfe.
«interface» Geraet
public interface Geraet
+ einschalten()
{
+ ausschalten()
public void einschalten();
+ istBereit(): Boolean
public void ausschalten();
public boolean istBereit();
Abbildung 5: Schnittstelle in UML
}
Nun können verschiedene Klassen diese Schnittstelle implementieren. Auf diese Weise entsteht eine Sammlung
von Klassen, die alle eine bestimmte Funktionalität zur Verfügung stellen: Alle Geräte kann man ein- und ausschalten und darauf überprüfen, ob sie benutzt werden können.
public class Kuehlschrank implements Geraet
{
heigene Attribute und Methodeni
public void einschalten()
{
heigene Implementierung dieser Methodei
}
«interface» Geraet
+ einschalten()
+ ausschalten()
+ istBereit(): Boolean
public void ausschalten()
{
heigene Implementierung dieser Methodei
}
+
+
+
+
public boolean istBereit()
{
heigene Implementierung dieser Methodei
}
Kuehlschrank
einschalten()
ausschalten()
istBereit(): Boolean
Abbildung 6: Implementierte
UML
}
Schnittstelle
in
Bspw. verhält sich ein Kühlschrank wie ein Gerät oder kann wie ein Gerät benutzt werden. Er implementiert
also die Schnittstelle Geraet , was in Java durch das Schlüsselwort implements und in UML durch eine
gestrichelte Linie mit einer leeren Pfeilspitze, wie in Abbildung 6 zu sehen, ausgedrückt wird.
10
Herunterladen