klassen2:interfaces:start
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen angezeigt.
— | klassen2:interfaces:start [2024/08/31 10:03] (aktuell) – angelegt - Externe Bearbeitung 127.0.0.1 | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
+ | ====== Interfaces ====== | ||
+ | {{ youtube> | ||
+ | Stellen wir uns ein Bibliotheksprogramm vor, das verschiedene Listen ausgeben kann: | ||
+ | * eine Liste aller Bücher der Bibliothek | ||
+ | * eine Liste aller Ausleiher/ | ||
+ | * eine Liste aller säumigen Ausleiher/ | ||
+ | * usw. | ||
+ | Das Bibliotheksprogramm wird eine Klasse '' | ||
+ | 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// | ||
+ | * eine Liste aller Bücher der Bibliothek //als pdf-Datei// ausgeben | ||
+ | * eine Liste aller Bücher der Bibliothek //als Html-Datei// | ||
+ | * eine Liste aller Bücher der Bibliothek //als csv-Datei// ausgeben | ||
+ | * eine Liste aller Ausleiher/ | ||
+ | * eine Liste aller Ausleiher/ | ||
+ | * eine Liste aller Ausleiher/ | ||
+ | usw. \\ \\ | ||
+ | <WRAP center round tip 60%> | ||
+ | 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: | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===== Bibliotheksprogramm: | ||
+ | Die Lösung des obigen Problems besteht in der Trennung der Aufgaben ([[https:// | ||
+ | <code learnj> | ||
+ | interface Ausgeber { | ||
+ | void schreibeZellwert(String wert); | ||
+ | void neueZeile(); | ||
+ | } | ||
+ | </ | ||
+ | <WRAP center round info 60%> | ||
+ | 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 '' | ||
+ | </ | ||
+ | In unserem Fall erwarten wir vom Ausgabeobjekt, | ||
+ | |||
+ | ===== Bibliotheksprogramm: | ||
+ | Mit dem Interface alleine kann man nichts anfangen, denn es besitzt noch keinen Programmcode, | ||
+ | < | ||
+ | |||
+ | <div class=" | ||
+ | |||
+ | <script type=" | ||
+ | class BildschirmAusgeber implements Ausgeber { | ||
+ | void schreibeZellwert(String wert) { | ||
+ | print(wert + " "); | ||
+ | } | ||
+ | void neueZeile() { | ||
+ | println(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | <script type=" | ||
+ | interface Ausgeber { | ||
+ | void schreibeZellwert(String wert); | ||
+ | void neueZeile(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | Die Festlegung '' | ||
+ | |||
+ | ==== Weitere Implementierungsvarianten ==== | ||
+ | Wir schreiben weitere Klassen, die das Interface implementieren: | ||
+ | |||
+ | < | ||
+ | |||
+ | <div class=" | ||
+ | |||
+ | <script type=" | ||
+ | class BildschirmAusgeber implements Ausgeber { | ||
+ | void schreibeZellwert(String wert) { | ||
+ | print(wert + " "); | ||
+ | } | ||
+ | void neueZeile() { | ||
+ | println(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | <script type=" | ||
+ | class HtmlAusgeber implements Ausgeber { | ||
+ | | ||
+ | |||
+ | void schreibeZellwert(String wert) { | ||
+ | if(!zeileBegonnen) print("< | ||
+ | zeileBegonnen = true; | ||
+ | print("< | ||
+ | } | ||
+ | void neueZeile() { | ||
+ | println("</ | ||
+ | zeileBegonnen = false; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | <script type=" | ||
+ | class CSVAusgeber implements Ausgeber { | ||
+ | void schreibeZellwert(String wert) { | ||
+ | print(" | ||
+ | } | ||
+ | void neueZeile() { | ||
+ | println(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | <script type=" | ||
+ | interface Ausgeber { | ||
+ | void schreibeZellwert(String wert); | ||
+ | void neueZeile(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | ===== Bibliotheksprogramm: | ||
+ | Der Listengenerator soll mit jeder der obigen Ausgabeklassen zusammenarbeiten können. Wir geben ihm daher ein Attribut '' | ||
+ | Wir lassen das Attribut '' | ||
+ | < | ||
+ | |||
+ | <div class=" | ||
+ | |||
+ | <script type=" | ||
+ | Listengenerator bp = new Listengenerator(); | ||
+ | bp.setzeAusgeber(new BildschirmAusgeber()); | ||
+ | bp.schreibeBücherListe(); | ||
+ | println(); | ||
+ | |||
+ | bp.setzeAusgeber(new HtmlAusgeber()); | ||
+ | bp.schreibeBücherListe(); | ||
+ | println(); | ||
+ | |||
+ | bp.setzeAusgeber(new CSVAusgeber()); | ||
+ | bp.schreibeBücherListe(); | ||
+ | println(); | ||
+ | |||
+ | |||
+ | |||
+ | class Listengenerator { | ||
+ | |||
+ | | ||
+ | |||
+ | void setzeAusgeber(Ausgeber ausgeber) { | ||
+ | this.ausgeber = ausgeber; | ||
+ | } | ||
+ | |||
+ | void schreibeBücherListe() { | ||
+ | ausgeber.schreibeZellwert(" | ||
+ | ausgeber.schreibeZellwert(" | ||
+ | ausgeber.neueZeile(); | ||
+ | ausgeber.schreibeZellwert(" | ||
+ | ausgeber.schreibeZellwert(" | ||
+ | ausgeber.neueZeile(); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | <script type=" | ||
+ | class BildschirmAusgeber implements Ausgeber { | ||
+ | void schreibeZellwert(String wert) { | ||
+ | print(wert + " "); | ||
+ | } | ||
+ | void neueZeile() { | ||
+ | println(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | <script type=" | ||
+ | class HtmlAusgeber implements Ausgeber { | ||
+ | | ||
+ | |||
+ | void schreibeZellwert(String wert) { | ||
+ | if(!zeileBegonnen) print("< | ||
+ | zeileBegonnen = true; | ||
+ | print("< | ||
+ | } | ||
+ | void neueZeile() { | ||
+ | println("</ | ||
+ | zeileBegonnen = false; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | <script type=" | ||
+ | class CSVAusgeber implements Ausgeber { | ||
+ | void schreibeZellwert(String wert) { | ||
+ | print(" | ||
+ | } | ||
+ | void neueZeile() { | ||
+ | println(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | <script type=" | ||
+ | interface Ausgeber { | ||
+ | void schreibeZellwert(String wert); | ||
+ | void neueZeile(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | <WRAP center round tip 60%> | ||
+ | Im Beispiel oben gibt es nur eine **einzige** Methode '' | ||
+ | </ | ||
+ | |||
+ | ===== Klassendiagramm ===== | ||
+ | {{ : | ||
+ | Ein Interface sieht im UML-Diagramm aus wie eine Klassenkarte, | ||
+ | |||
+ | ===== Aufgabe 1 ===== | ||
+ | Schreibe eine Klasse '' | ||
+ | <code javascript> | ||
+ | [ ' | ||
+ | [ ' | ||
+ | </ | ||
+ | ====== Anwendung: Timer ====== | ||
+ | <WRAP center round info 60%> | ||
+ | Wenn Du möchtest, dass eine Methode periodisch nach immer gleichen Zeitabständen aufgerufen wird, kannst Du dafür die '' | ||
+ | <code java> | ||
+ | | ||
+ | </ | ||
+ | bekommt als Parameter ein Objekt übergeben, das das Interface '' | ||
+ | <code java> | ||
+ | interface Runnable{ | ||
+ | |||
+ | public void run(); | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | Diese Methode wird von der Klasse '' | ||
+ | </ | ||
+ | |||
+ | |||
+ | < | ||
+ | |||
+ | <div class=" | ||
+ | |||
+ | <script type=" | ||
+ | Timer.repeat(new Zähler(), 500); | ||
+ | |||
+ | class Zähler implements Runnable { | ||
+ | |||
+ | int n = 1; | ||
+ | |||
+ | public void run() { | ||
+ | | ||
+ | | ||
+ | n++; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | ===== 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 '' | ||
+ | |||
+ | ====== Flächenberechnung mittels Monte-Carlo-Simulation ====== | ||
+ | {{ : | ||
+ | {{: | ||
+ | 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 == | ||
+ | {{ : | ||
+ | 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 '' | ||
+ | Ü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: | ||
+ | <code java> | ||
+ | interface Fläche { | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Wir schreiben eine Klasse Einheitskreis, | ||
+ | <WRAP center round tip 60%> | ||
+ | **Erinnerung: | ||
+ | </ | ||
+ | |||
+ | |||
+ | < | ||
+ | |||
+ | <div class=" | ||
+ | |||
+ | <script type=" | ||
+ | Flächenberechner fb = new Flächenberechner(); | ||
+ | fb.berechneFlächenInhalt(new Einheitskreis()); | ||
+ | |||
+ | |||
+ | class Einheitskreis implements Fläche { | ||
+ | | ||
+ | return x * x + y * y <= 1; | ||
+ | } | ||
+ | |||
+ | | ||
+ | return -1; | ||
+ | | ||
+ | |||
+ | | ||
+ | return 1; | ||
+ | | ||
+ | |||
+ | | ||
+ | return -1; | ||
+ | | ||
+ | |||
+ | | ||
+ | return 1; | ||
+ | | ||
+ | |||
+ | } | ||
+ | |||
+ | interface Fläche { | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | <script type=" | ||
+ | public class Flächenberechner { | ||
+ | |||
+ | | ||
+ | | ||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | | ||
+ | | ||
+ | double rechtecksBreite = fläche.getXMax() - fläche.getXMin(); | ||
+ | double rechtecksHöhe = fläche.getYMax() - fläche.getYMin(); | ||
+ | | ||
+ | initGraphik(fläche, | ||
+ | | ||
+ | double drin = 0; | ||
+ | double flächeUmgebendesRechteck = rechtecksBreite * rechtecksHöhe; | ||
+ | |||
+ | for(int n = 1; n <= 500000; n++) { | ||
+ | |||
+ | // zufällige Zahl zwischen xMin und xMax | ||
+ | | ||
+ | |||
+ | // zufällige Zahl zwischen yMin und yMax | ||
+ | | ||
+ | |||
+ | | ||
+ | drin++; | ||
+ | zeichnePunkt(x, | ||
+ | } else { | ||
+ | zeichnePunkt(x, | ||
+ | } | ||
+ | |||
+ | // Nach je 10000 Punkten Ausgabe des Zwischenstands: | ||
+ | if(n % 10000 == 0) { | ||
+ | | ||
+ | double näherungswert = flächeUmgebendesRechteck / n * drin; | ||
+ | | ||
+ | println(" | ||
+ | print(" | ||
+ | println(näherungswert, | ||
+ | | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | | ||
+ | new World(1000, 1000); | ||
+ | double dMax = dx; | ||
+ | if(dy > dx) dMax = dy; | ||
+ | bitmap = new Bitmap(pixelZahl, | ||
+ | | ||
+ | zeichenFaktor = pixelZahl / dMax; | ||
+ | xMin = fläche.getXMin(); | ||
+ | yMin = fläche.getYMin(); | ||
+ | |||
+ | bitmap.fillAll(Color.black); | ||
+ | } | ||
+ | |||
+ | | ||
+ | | ||
+ | int ix = Math.round((x - xMin) * zeichenFaktor); | ||
+ | int iy = pixelZahl - Math.round((y - yMin) * zeichenFaktor); | ||
+ | |||
+ | bitmap.setColor(ix, | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | </ | ||
+ | <WRAP center round tip 60%> | ||
+ | **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 '' | ||
+ | * 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 " | ||
+ | * c) Für die Interessierten: | ||
+ | |||
+ | <WRAP center round info 60%> | ||
+ | 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: | ||