====== Interfaces ====== {{ youtube>dn6FT_g8OZk?large }} Stellen wir uns ein Bibliotheksprogramm vor, das verschiedene Listen ausgeben kann: * eine Liste aller Bücher der Bibliothek * eine Liste aller Ausleiher/innen * eine Liste aller säumigen Ausleiher/innen * usw. Das Bibliotheksprogramm wird eine Klasse ''Listenausgabe'' haben, die für jede der obigen Listen eine Methode besitzt. \\ Von einem guten Programm würden wir erwarten, dass es diese Listen auf dem Bildschirm, als pdf-Datei, als Html-Datei oder als csv-Datei (zur Verwendung in Tabellenkalkulationen) ausgeben kann. Es müsste also jede der obigen Methoden mehrfach geben: * eine Liste aller Bücher der Bibliothek //auf dem Bildschirm// ausgeben * eine Liste aller Bücher der Bibliothek //als pdf-Datei// ausgeben * eine Liste aller Bücher der Bibliothek //als Html-Datei// ausgeben * eine Liste aller Bücher der Bibliothek //als csv-Datei// ausgeben * eine Liste aller Ausleiher/innen //auf dem Bildschirm// ausgeben * eine Liste aller Ausleiher/innen //als pdf-Datei// ausgeben * eine Liste aller Ausleiher/innen //als Html-Datei// ausgeben usw. \\ \\ Ist es wirklich nötig, für jede Ausgabemöglichkeit jede der Listenmethoden erneut zu schreiben? Der Programmcode der ersten vier Methoden wird doch größtenteils identisch sein, ebenso der der nächsten vier Methoden usw.! \\ \\ **Klar geht es einfacher:** Für solche Zwecke verwenden wir **Interfaces**. ===== Bibliotheksprogramm: interface Ausgeber ===== Die Lösung des obigen Problems besteht in der Trennung der Aufgaben ([[https://en.wikipedia.org/wiki/Separation_of_concerns|separation of concerns]]): Das Bibliotheksprogramm besitzt eine Klasse ''Listengenerator'', die zur Ausgabe jeder Listenart (Bücherliste, Ausleiherliste, ...) je eine Methode besitzt. Dieser Klasse stellen wir ein Objekt (im folgenden kurz: Ausgabeobjekt) zur Verfügung, das sich darum kümmert, in welcher **Form** die Listen ausgegeben werden (auf dem Bildschirm, als pdf-Datei, ...). Damit die Methoden der Klasse Listengenerator dieses Ausgabeobjekt nutzen können, müssen sie "wissen", welche Methoden es besitzt. Diese Vereinbarung wird in einem Interface getroffen: interface Ausgeber { void schreibeZellwert(String wert); void neueZeile(); } Ein **interface** ist eine Vereinbarung darüber, welche Methoden eine Klasse mindestens besitzen soll. Im Interface werden die Methoden nur deklariert, d.h. es wird angegeben, welchen Bezeichner ( = Namen) sie tragen, welche Parameter sie benötigen und welchen Datentyp ihr Rückgabewert hat. Der Methodenrumpf (d.h. die Anweisungen in ''{'' ... ''}'') wird weggelassen. Jede Methodendeklaration in einem Interface wird durch ein Semikolon ('';'') abgeschlossen. In unserem Fall erwarten wir vom Ausgabeobjekt, dass es Tabellen ausgeben kann. Dazu besitzt es eine Methode zur Ausgabe von Text in einer Tabellenzelle und eine Methode, um eine neue Tabellenzeile zu beginnen. \\ ===== Bibliotheksprogramm: Implementierungsvarianten des Interfaces ===== Mit dem Interface alleine kann man nichts anfangen, denn es besitzt noch keinen Programmcode, der die Aufgaben wirklich erledigen kann. Wir müssen Klassen schreiben, die genau die Methoden besitzen, die das Interface definiert. Man sagt: Diese Klassen **implementieren** das Interface. Wir beginnen mit einer Klasse, die eine Tabelle auf dem Bildschirm ausgeben kann:
Die Festlegung ''implements Ausgeber'' in Zeile 1 bedeutet: "Die Klasse ''Bildschirmausgeber'' besitzt alle im Interface ''Ausgeber'' definierten Methoden. Lösche im oberen Beispiel mal eine der beiden Methoden der Klasse ''Bildschirmausgeber'' raus und schau' Dir an, was passiert! ==== Weitere Implementierungsvarianten ==== Wir schreiben weitere Klassen, die das Interface implementieren:
===== Bibliotheksprogramm: Listengenerator ===== Der Listengenerator soll mit jeder der obigen Ausgabeklassen zusammenarbeiten können. Wir geben ihm daher ein Attribut ''ausgeber'' vom Typ ''Ausgeber''. Dieses Attribut kann auf beliebige Objekte zeigen, die das Interface ''Ausgeber'' implementieren. Daher lassen sich mit diesem Attribut nur diejenigen Methoden aufrufen, die das Interface ''Ausgeber'' definiert. \\ Be Wir lassen das Attribut ''ausgeber'' zuerst auf ein Objekt der Klasse ''Bildschirmausgeber'' zeigen und rufen die Methode ''schreibeBücherListe()'' auf. Anschließend wiederholen wir dasselbe mit den beiden anderen Ausgeber-Klassen.
Im Beispiel oben gibt es nur eine **einzige** Methode ''schreibeBücherListe()'', die sich nicht darum kümmern muss, in welcher Form ihre Ausgabe ausgegeben wird. Wir rufen sie dreimal auf und erhalten drei unterschiedlich formatierte Ausgaben, indem wir einfach nur jedesmal ein anderes Objekt zur Verfügung stellen, das das Interface ''Ausgeber'' implementiert. ===== Klassendiagramm ===== {{ :klassen2:interfaces:klassendiagramm_interfaces.png?600 |}} Ein Interface sieht im UML-Diagramm aus wie eine Klassenkarte, der Bezeichner ist jedoch kursiv geschrieben und durch ein vorangestelltes //<>// als solches gekennzeichnet. Dass eine Klasse ein Interface implementiert, kennzeichnet man im UML-Diagramm durch eine gestrichelte Verbindungslinie, die beim Interface in ein Dreieck mündet. ===== Aufgabe 1 ===== Schreibe eine Klasse ''JSonAusgeber'', die das Interface ''Ausgeber'' implementiert und Tabellen im [[https://en.wikipedia.org/wiki/JSON|JSon-Format]] als Liste ausgibt, also so: [ 'Zeile1, Spalte1', 'Zeile1, Spalte2' ], [ 'Zeile2, Spalte1', 'Zeile2, Spalte2' ] ====== Anwendung: Timer ====== Wenn Du möchtest, dass eine Methode periodisch nach immer gleichen Zeitabständen aufgerufen wird, kannst Du dafür die ''Timer''-Klasse benutzen. Die Methode Timer.repeat(runnable, deltaTimeInMs); bekommt als Parameter ein Objekt übergeben, das das Interface ''Runnable'' implementiert und damit gesichert eine Methode ''run()'' besitzt: interface Runnable{ public void run(); } Diese Methode wird von der Klasse ''Timer'' nach jeweils ''deltaTimeInMs'' Millisekunden immer wieder aufgerufen.
===== Aufgabe 2 ===== Schreibe ein Programm, dass ständig die seit dem Programmstart vergangene Zeit in Minuten und Sekunden (Beispiel: 03:12) anzeigt. \\ //Tipp: Die Methode ''SystemTools.clearScreen()'' löscht den Textausgabebereich.// ====== Flächenberechnung mittels Monte-Carlo-Simulation ====== {{ :klassen2:interfaces:pasted:20200521-225753.png}} {{:klassen2:interfaces:monte-carlo_1.png?400 |}} Wir wollen Inhalte ebener, begrenzter Flächen mithilfe der Monte-Carlo-Simulation berechnen. Am Beispiel der Fläche des Einheitskreises (Kreis um den Punkt (0,0) mit Radius 1) erkläre ich Dir, wie das geht. == 1. Schritt: Wir umgeben die Fläche mit einem achsenparallelen Rechteck == {{ :klassen2:interfaces:monte-carlo_2.png?400|}} In diesem Fall wählen wir das Rechteck, das von $x_{min} = -1$, $x_{max} = 1$, $y_{min} = -1$ und $y_{max} = 1$ begrenzt ist. == 2. Schritt: Wir platzieren sehr viele zufällige Punkte ins Rechteck == Wie das aussieht, siehst Du an der Grafik rechts oben. Ein Teil der Punkte liegt in der zu bestimmenden Fläche (grün), ein Teil außerhalb (rot). == 3. Schritt: Flächenberechnung == Angenommen, wir haben 10000 Punkte gesetzt, davon liegen 7894 im Einheitskreis. Dann lässt sich ein guter Näherungswert für seinen Flächeninhalt so berechnen: $$ Fläche_{Kreis} = \frac{Fläche_{Rechteck}}{10000} \cdot 7894 = \frac{(x_{max} -x_{min}) \cdot (y_{max} -y_{min})}{10000}\cdot 7894 = \frac{4}{10000}\cdot 7894 \approx 3,1576$$ == 4. Schritt: Punktezahl erhöhen == Da die Punkte zufällig gewählt sind, ergibt sich bei jedem Programmablauf ein anderer Wert für die Kreisfläche. Dieser nähert sich bei zunehmender Punktezahl aber mit immer größerer Wahrscheinlichkeit immer mehr dem exakten Wert ($\pi\cdot r^2 = \pi\cdot 1^2\approx 3,141592$) an. Meldung eines beispielhaften Programmablaufs: * Nach 10000 Schritten liegen 7839 Punkte in der Fläche. Näherungswert nach 10000 Schritten: 3.1356 * Nach 20000 Schritten liegen 15674 Punkte in der Fläche. Näherungswert nach 20000 Schritten: 3.1348000000000003 * Nach 30000 Schritten liegen 23431 Punkte in der Fläche. Näherungswert nach 30000 Schritten: 3.1241333333333334 * Nach 40000 Schritten liegen 31303 Punkte in der Fläche. Näherungswert nach 40000 Schritten: 3.1303 * Nach 50000 Schritten liegen 39170 Punkte in der Fläche. Näherungswert nach 50000 Schritten: 3.1336000000000004 ==== Programm ==== Die Klasse ''Flächenberechner'' verfügt über eine Methode ''berechneFlächenInhalt(Fläche fläche)'', die Mittels der Monte-Carlo-Methode den Flächeninhalt der als Parameter übergebenen Fläche berechnen kann. Aber wie soll so ein ''Fläche''-Objekt beschaffen sein, d.h. welche Methoden soll es haben? \\ Überlegen wir, was wir zur Durchführung der Monte-Carlo-Methode benötigen: * Wir müssen ein achsenparalleles Rechteck um die Fläche ziehen, brauchen also die Werte $x_{min}$, $x_{max}$, $y_{min}$ und $y_{max}$. * Für jeden der zufällig bestimmten Punkte im Rechteck müssen wir wissen, ob er in der zu messenden Fläche liegt oder nicht. Diese Anforderungen formulieren wir als Interface: interface Fläche { public boolean punktLiegtDrin(double x, double y); public double getXMin(); public double getXMax(); public double getYMin(); public double getYMax(); } Wir schreiben eine Klasse Einheitskreis, die das Interface implementiert und dem Einheitskreis entspricht. **Erinnerung: ** \\ Ein Punkt $(x, y)$ liegt genau dann im Einheitskreis, wenn $\sqrt{x^2 + y^2} \le 1$, also wenn $x^2 + y^2 \le 1$.
**Tipp:** Lass' das Programm mit sehr höher Geschwindigkeit ablaufen, sonst dauert es ewig, bis Du etwas siehst. ===== Aufgabe 3 (freiwillig) ===== Übernimm den Code für das Interface ''Fläche'' und die Klasse ''Flächenberechner'' und programmiere dazu * a) Eine Klasse Dreieck, die das Dreieck mit den Eckpunkten $(0,0)$, $(0,1)$ und $(1,0)$ repräsentiert. * b) Eine Klasse Sinus, die dem Flächenstück unter dem ersten "Buckel" der Sinusfunktion entspricht. \\ Berechne mit Hilfe dieser beiden Klassen näherungsweise den jeweiligen Flächeninhalt. * c) Für die Interessierten: Versuche, die Klasse ''Flächenberechner'' zu verstehen! Das Monte-Carlo-Verfahren verwendet man in der Praxis natürlich nicht für so einfache ebene Flächen wie die Beispiele oben, sondern nur zur Berechnung des Flächeninhalts komplex gestalteter Flächen in mehrdimensionalen Räumen. [[.loesung:start|Hier geht's zur Lösung.]]