Gut, also unser erstes Rätsel.
Ich schalte das Ding wieder ein.
Also erst müssen wir uns die Datenstücke anschauen, die hier
vorkommen.
Die brauchen wir nämlich.
Ein zweidimensionales Feld.
Feld von Vektoren.
Eins, zwei, drei, vier, fünf Stück davon.
Anzahl Testfelder, das ist ein Zähler, der läuft.
Das ist zu null initialisiert, auch prima.
Mal schauen, was als erstes...
Das ist die Hauptfunktion, die läuft natürlich als erstes ab.
Hier ist ein Try-Ding.
Oh Gott, was macht denn das wieder?
Ist gleich null.
Sagt für immer, suche was raus.
Nämlich, falls Testfälle, drittes Element hat Wert 3, Testfälle i++,
das heißt Postinkrement, wohlgemerkt.
Das i wird erst gerechnet und hier in dem Index ausgedrückt.
Und wenn alles vorbei ist, dann erst das plus plus gerechnet.
Also auch okay.
Und falls es wahr gibt, ist die Anzahl Testfälle plus plus.
Dann schauen wir noch das Ding an, drittes Element hat Wert 3.
Dann gebe ich ein Feld und dann macht das nichts weiter, als das
dritte Element zu berücksichtigen, falls das Feld lang genug ist.
Was passiert eigentlich hier?
Wann hört diese Schleife auf?
Wer kriegt es raus?
Schleife terminiert, aber warum?
Beispiel von obskurer Programmierung.
Ja, bitte.
Genauer.
Wir sind auf dem richtigen Weg, aber das ist ja auch mal 2, dann ist
es 3, dann ist es 4, dann ist es nur 1.
Das größere Array, genau, das äußere, über den Index 0, 1, 2, 3, 4
hinausgeht, also 5, bricht diese Schleife ab, weil dann diese
Arrayindex out of bounds exception aufgerufen wird und da ist jetzt
nichts mehr zu tun, denn das ist unsere Abbruchsbedingung.
Also, so programmiert man normalerweise nicht.
Der Fehler 1, nämlich eine Schleife durch eine Ausnahme zu beendigen.
Aber schauen wir mal, was passiert denn eigentlich, wenn wir das
Programm ausführen?
Geht das noch weiter?
Das Ding noch einstecken, das würde helfen.
Und siehe da, die möglichen Antworten.
Was wird ausgegeben?
Eine 2?
Laufzeitfehler, Arrayindex out of bounds exception oder was anderes?
Wie könnte die 2 entstehen?
1, 2, 1, 2, 1, 2, da ist einer, da ist einer.
An der richtigen Stelle.
Also, es ist etwas anderes, nämlich 0.
Kann jemand erraten?
Irgendjemand mit einer 0?
Keiner.
Also gut, was passiert?
Also, das Durchlaufen durch die Feldteststelle wird mit Hilfe einer
Ausnahme gesteuert.
Das funktioniert in diesem Falle, aber Sie haben gesehen, Sie haben
nicht gemerkt, was hier passiert.
Eine obskure Art der Programmierung sollte man nicht machen.
Wenn Sie ein Feld durchsuchen, dann nehmen Sie eine ganz normale
Schleife und zählen den Index hoch.
Der nächste Ausdruck, der einen Fehler auslöst, ist der hier.
Feldgröße gleich 3 und Feld ist gleich 3.
Das heißt, wir gehen mal hier zurück.
Dieser Ausdruck hier ist verkehrt.
Da müsste und, und stehen.
Denn, wenn die Länge des Feldes nicht ausreicht, wird trotzdem das
hier ausgewertet.
Nehmen wir an, wir haben das Feld der Länge 1, haben wir 1.
Da fehlt die erste Bedingung, ist falsch, aber das zweite Bedingung
wird trotzdem ausgewertet und die führt wieder zu einem Array-Index
-Out -of-Bounds-Exception.
Das heißt, wir fliegen bereits hier raus.
Wir kommen nicht weiter als bis zu diesem Argument.
Weil hier natürlich der Array-Index-Out-of-Bounds-Exception zweimal
potenziell ausgelöst wird.
Wir müssen jetzt einmal in das umfassende Feld herausfliegen und
einmal im kleineren.
Wie korrigieren wir es?
Hier ist erstmal eine ordentliche Schleife.
Da kann man auch lesen, was da passiert.
I kleiner Testfälle Punkt Längth.
Testfälle ist dieses äußere Feld.
Dann rufen wir dieses Testfälle I auf.
Ohne I++ und wir müssen dann auch das I++ hier nicht mehr schreiben,
denn das macht unsere Schleife schon.
Das hat auch diese Schwierigkeit beseitigt.
Unschönheit.
Und schließlich hier ein Und-Und-Einsatz.
Dann läuft die Geschichte richtig.
Also, was lernen wir daraus?
Ausnahmen nicht zur Steuerung von Schleifen benutzen.
Vorsichtig bei der Verwendung von Und-Und-Oder-Operationen, ob Sie sie
nicht die Kurzauswertungen nehmen sollten, die Und-Und-Oder-Oder-Oder.
Wenn Sie jetzt nicht wissen, wovon ich rede, dann schauen Sie bitte
nach.
Außerdem, Sie sehen hier, dass ich auf Index-Out-of-Bounds-Exception
warte, in dem Originalprogramm, es aber nirgends erklärt ist.
Zum Beispiel, wenn ich zurückgehe, Moment, ich brauche gar nicht so
weit zurück.
Es wird zum Beispiel hier in dieser Methode nicht erklärt.
Normalerweise müsste man schreiben, Static Boolean drittes Element hat
Wert 3 bla bla,
throws Index-Out-of-Bounds-Exception.
Allerdings ist diese Ausnahme eine besondere, nämlich eine, die heißt
Unchecked -Exception, das heißt der Compiler überprüft nicht, ob sie
auch wirklich deklariert ist und dann an einer anderen Stelle
aufgefangen wird.
Hätte man noch machen können, hätte auch nichts geschadet.
Dann wäre vielleicht noch ein bisschen Klarheit mehr gewesen in dem
ursprünglichen Programm.
Aber wir kommen auch ohne dem ganz gut zurecht, denn wir wissen jetzt
bei der korrigierten Form, kann hier überhaupt das Problem der Index
-Out -of-Bounds-Exception gar nicht mehr auftreten, weil wir hier
schon abbrechen mit der Auswertung und falsch zurückgeben.
Okay, gut.
Fragen dazu?
So, das war mal ein Beispiel für ein obskures Programm.
Also noch mal nachschauen, was und und und oder oder bezeichnet und
vielleicht auch die Ausnahmen.
Catch-and-Throws anschauen.
So, dann machen wir weiter mit den Turfsmustern.
In der letzten Übung sind wir schon ein Stückchen weiter gekommen.
Es kommen eigentlich heute nur noch einfache Muster dran, bis auf das
allererste.
Das erste ist ein klein bisschen schwieriger.
Da geht es darum, wenn man sehr viele Objekte hat, die man innerhalb
eines Programmes anlegen muss und dafür ein bisschen Platz sparen
möchte.
Das Platz sparen rückt sich so aus, dass man sagt, es gibt zwar sehr
sehr viele Tausende, Zehntausende von Objekten, aber viele davon haben
einen Zustand, eine Attribute, die die gleichen Werte haben.
Könnte ich die Objekte nicht kleiner machen, indem ich diese Attribute
herausnehme und woanders speichere und sie dann gewissermaßen für
mehrere Objekte gleichzeitig beibehalte?
Das zeige ich Ihnen am Beispiel.
Und zwar stellen wir uns vor, wir bauen einen Texteditor, ein
Superword oder so etwas und sagen, wir machen das jetzt ganz sauber,
wir machen Objektorientierung von Anfang an und wir führen natürlich
auch sofort einzelne Zeichen sogar als Objekte ein.
Also zum Beispiel, wenn ich hier das Beispielwort hätte, das Wort
Beispiel, sehen Sie natürlich, dass das E zweimal vorkommt.
Und ich kann jetzt natürlich hergehen und sagen, gut, dann müsste ich
jetzt im Idealfall eben für jedes Dokument, dann stellen Sie sich vor,
das ist eine Diplomarbeit von Ihnen, vielleicht 60 oder 80 Seiten
lang, da sind ziemlich viele Buchstaben drin, habe ich also tausende
von diesen Objekten und das kann natürlich sein, dass mein Programm,
entweder der Speicher nicht dafür reicht, bei großen Dateien, oder der
Speicher schon auf meinem mobilen Gerät nicht dafür reicht.
Auf der anderen Seite würde es doch eigentlich reichen, wenn ich diese
Objekte, den gesamten Zeichenvorrat, 30 Zeichen, mit Großbuchstaben
vielleicht 60 Zeichen, lassen wir es 128 Zeichen sein, aber nur alle
einmal speichere und dafür einen Code benutze, nämlich den Unicode
oder auch nur den ASCII-Code, um sie zu indizieren.
Nur, ich brauche für jedes Zeichen mehr als nur das Zeichen selbst.
Ich muss zum Beispiel wissen, ob ein Zeichen wie das B wertgeschrieben
sein soll, Kursiv, beides, unterstrichen, ich brauche die
Schriftgröße, ich brauche die Farbe.
All diese Informationen brauche ich eigentlich für jedes Zeichen.
Nun ist es aber so, dass die Schriftgröße sich nicht von einem Zeichen
zum nächsten ändert, sondern meistens haben sie ziemlich lange
Absätze, mehrere, und dann kommt vielleicht an irgendeiner Stelle mal
ein Wort, das hervorgehoben wird, nämlich eine Überschrift, die muss
dann größer sein.
Für die Überschrift wechseln Sie auch nicht von Groß auf Klein und
dann wieder auf Groß und von Grün auf Blau und von dort nach Gelb,
sondern Farbe, Größe, Zeichensatz usw.
bleiben über einen längeren Zeitraum erhalten.
Wenn ich jetzt aber die Information rausnehme, dann kann ich nicht
mehr unterscheiden.
Von daher ist jetzt die Idee des Fliegengewichts zu sagen, gut, ich
habe ein sogenanntes, ja, ich sage es mal, irgendwie kommt mir das
unscharf vor.
Kann man das irgendwie schärfer stellen, oder täusche ich mich?
Oh, jetzt kann man das lesen.
Das ist schon sehr viel besser.
Danke.
So, da sagen wir jetzt, ich kann ein getrennt benutzbares
Fliegengewicht haben, das ist ein vollständiges Objekt, das enthält
alles, was ich brauche, also den Zeichencode, die Größe, die Farbe,
den Stil, in dem ich es angezeigt haben will und den Zeichensatz, alle
fünf oder sechs oder vielleicht noch mehr kommen hier drin vor.
Ich habe also den gesamten Zustand, spart mir gar nichts, aber
manchmal brauche ich das.
Dann gibt es ein anderes, konkretes Fliegengewicht, das hat einen
Zustand, der immer neu ist, gebraucht wird, der wäre in unserem Fall
eigentlich nur der Zeichencode.
Welches Zeichen ist es?
Im Unicode wahrscheinlich.
Und dann brauche ich noch Operationen, wenn die an extrinsischem
Zustand arbeiten, das nennt mich einen Zustand, den ich ausgelagert
habe, diesen ausgelagerten Zustand, an dem ich arbeiten möchte,
eventuell zum Beispiel die Schriftgröße ändern, dann brauche ich dazu
irgendwie einen Zugang, nämlich einen Zeiger, auf einen Behälter, der
für mehrere Zeichen diesen extrinsischen Zustand vorhält.
Das ist gerade bei unserem Beispiel, bei unserem Editor-Beispiel,
ziemlich praktisch, denn ich werde einfach sagen, wenn ich eine Spalte
anlege in meinem Editor, dann besteht die natürlich aus einzelnen
Zeilen, und in dieser Spalte lege ich mal die Größe und die anderen
Attribute fest und schreibe also drauf los, wenn ich an eine Stelle
komme, wo der Benutzer sagt, so und jetzt aber, was ändern, die Farbe
ändern, die Größe ändern, den Zeichensatz ändern, dann sage ich, gut,
dann vermerke ich mir in meiner Spalte, dass ab Position so und so der
Zeichensatz geändert wurde.
Das heißt, wenn ich jetzt irgendwo in meinem Absatz, in meiner Spalte,
einen Buchstaben drucken möchte oder anzeigen möchte, weiß ich immer,
wenn es nicht ein Objekt ist, das komplett vollständig gespeichert
wurde, sondern eines mit einem extrinsischen Zustand, dann schaue ich
in der Spalte nach, in der Position, da suche ich mir eine Indizio
-Operation, damit das Nachschauen an die richtige Position schnell
geht und hole mir da die Zusatzinformation.
Dann brauche ich sie also, wenn ich nichts ändere für hunderte oder
tausende von Zeichen, nur diese Information ein einziges Mal zu
speichern, anstelle sie hunderte oder tausende Mal.
So, jetzt schauen wir uns, wie das jetzt funktioniert.
Da brauchen wir eine Fliegengewicht-Fabrik, die entscheidet, was zu
machen ist.
Es kommt noch eine Zusatzinformation hinzu, ich lege die Objekte erst
an, wenn ich sie wirklich brauche, aber wenn ich Duplikate davon
brauche, dann lege ich kein neues Objekt an.
Also habe ich so ein Fliegengewicht, das sehe ich nach, ob der
Schlüssel, mit dem ich dieses Fliegengewicht adressiere, das wäre in
unserem Fall der Unicode, falls der schon existiert, dann gebe ich
dieses Fliegengewicht zurück.
Ein konkretes Fliegengewicht.
Andernfalls muss ich es halt neu erzeugen mit diesem Schlüssel und
füge es in die Menge der Fliegengewichte ein, gebe das neue
Fliegengewicht zurück.
In unserem Fall des Editors würde ich das aber nicht so dynamisch
machen, das ist nicht das Herz dieser Idee, sondern ich würde einfach
alle Buchstaben einmal anlegen, das ist zu erwarten, dass jeder
Buchstabe einmal vorkommt.
Ich brauche also das dynamische Anlegen, spart mir keine Zeit.
Wenn das nicht so wäre, in anderen Anwendungen, wo nicht alle Objekte
vorkommen, dann ist das natürlich eine andere Geschichte.
So, schauen wir nochmal zurück.
Objekte kleinster Granulität gemeinsam zu speichern, um große Mengen
von innen effizient speichern zu können.
Speicherplatzeffizient.
So, was haben wir hier noch zu sagen?
Gut, das habe ich erklärt.
Also, dieses Fliegengewicht-Idee benutzen wir, wenn wir eine große
Menge von Objekten innerhalb unseres Programmes anlegen müssen.
Wenn daher die Speicherkosten aufgrund dieser großen Anzahl
problematisch werden, wenn wir damit rechnen können, dass bei großen
Programmen, großen Datenmengen der Hauptspeicher nicht mehr reicht.
Wenn außerdem ein Großteil des Objektzustandes in einen Kontext
verlagert und damit extrinsisch gemacht werden kann.
Entweder ich muss dann in dem Objekt selbst einen Verweis haben, das
auf die Datenstruktur zeigt, die die extrinsische Information
verhaltet, dann gibt es eben viele Zeiger, viele Objekte, die diese
Information gemeinsam haben.
Oder aber, ich lege sie, wie in unserem Beispiel, überhaupt als
separate Datenstruktur an und weiß immer, wo ich hinschauen muss.
Wenn Sie jetzt nochmal unser Editor-Beispiel zurücknehmen, da machen
wir erst noch die Punkte fertig,
viele Gruppen von Objekten durch relativ wenig gemeinsame genutzte
Objekte ersetzt werden, das sind die Zeichen, die einzelnen Zeichen,
wenig gemeinsam genutzte Objekte ersetzt werden können, sobald einmal
der extrinsische Zustand entfernt wurde.
Das haben wir gerade bei den Zeichen kurz ausgenutzt.
Und ferner darf es natürlich nicht so sein, dass ich jetzt von der
Identität dieser Objekte abhänge, denn ich kann sie nachher nicht mehr
unterscheiden.
Der Schlüssel für gemeinsame Objekte ist der gleiche und
möglicherweise auch der extrinsische Zustand.
Jetzt wäre natürlich noch zu überlegen, in unserem Beispiel, wenn ich
jetzt aus irgendeinem Grunde erstmal alle Zeichen in den gleichen
Attributen brauche, also alles in Grün und Größe 10 und Times Roman
und Normalschrift und so weiter, und plötzlich entscheiden Sie sich in
dem fortlaufenden Text, wollen Sie plötzlich das Wort Beispielkursiv
setzen.
Da ändern Sie ja etwas.
Was Sie da machen, in Ihrem Verzeichnis über die Spalte, müssen Sie
jetzt eintragen, ab Position hier gilt jetzt ein anderer Stil, nämlich
Kursivschrift.
Und danach hier wird wieder auf Normalschrift zurückgesetzt.
Das kann ich, wie gesagt, so machen, indem ich die Spalte mit einer
Indizierfunktion versehe, was nicht so geschickt ist unbedingt, wenn
ich Dinge verschiebe, oder aber ich füge hier etwas Unsichtbares ein,
das sagt, so, und an dieser Stelle etwas anderes.
Und dann kann ich es natürlich auch erreichen, dass ich jeden
Buchstaben anders mache, wenn ich das so will, und ich spare damit
wieder weniger, wenn ich die Objekte mit zum Beispiel das genutzbare
Fliegengewicht hernehme, aber wenn das relativ selten vorkommt, dann
ist das auch kein Problem mehr.
Das war Anwendbarkeit.
Gibt es Fragen dazu?
Also, wie versprochen, das Nächste ist total einfach.
Das Nächste ist das sogenannte Memento.
Und was wir da machen, ist nichts weiteres als ein
Zwischenkopiespeichern eines Objektes.
Das brauche ich manchmal, wenn ich ein Objekt ändern möchte, aber dann
zu einem späteren Zeitpunkt sagen kann, und jetzt möchte ich den
ursprünglichen Zustand wieder zurückhaben.
Das brauche ich zum Beispiel bei einer Wiederherstellerfunktion oder
einer Undo-Funktion.
Wenn Sie in einem Editor, das kennen Sie ja, etwas verkehrtes getippt
haben und sagen dann, die entsprechenden Tastenkombinationen, was
immer es ist, Propeller Z oder sonst etwas, zurückkehren wollen, dann
muss da ein alter Zustand gespeichert worden sein.
Und in der Tat, das macht man mit einem sogenannten Memento.
Und die Programmierung des Mementos ist total einfach.
Ich kann also sagen, meinetwegen gibt es hier ein ursprüngliches
Objekt, das nennen wir den Urheber.
Dem gebe ich zwei zusätzliche Funktionen, nämlich den sicheren Zustand
und den restaurierten Zustand, oder Methode.
Der sichere Zustand liefert mir eine Kurzkopie dieses Objektes.
Die ist dann ohne die Operationen, ohne die Methoden.
Ich kann auch von diesen Zustandsvariablen vielleicht welche
weglassen, die nicht gesichert werden können, die wieder berechnet
werden können.
Machen wir also eine separate Klasse, die nur die Informationen, die
ich wirklich sichern muss, speichert.
Machen wir hier eine Funktion, die dieses Memento erzeugt, mir also
das Memento zurückliefert, die Sicherungskopie.
Ich brauche natürlich irgendjemanden, der das aufbewahrt.
Und dann gibt es noch eine Methode, die heißt restauriere, oder
restauriere den Zustand.
Dem muss ich ein Memento geben.
Dann wird sich dieses Objekt in ihren ursprünglichen Zustand, in den
gesicherten Zustand, zurückversetzen, indem es aus dem Memento die
Daten ausliest, über einen Gibzustand oder so etwas, Funktion, oder
auch Zugriff auf einzelne Attribute.
Sieht einfach aus, ist auch einfach.
Die Frage ist immer, was wäre die Alternative gewesen?
Die Alternative wäre gewesen, an der Stelle, wo ich eine Kopie von dem
Urheber haben will, lese ich einfach die ganzen Attribute aus und
speichere sie irgendwo ab.
Das hat aber den Nachteil, dass ich jetzt keinerlei private Attribute
benutzen dürfte.
Denn die kann ich nur an bestimmten Stellen auslesen und
möglicherweise nicht da, wo ich sie brauche.
Die Lösung, wo ich die Methode mit dem Objekt, mit der Klasse
verknüpfe, stellt sicher, dass die sichere Zustand, restaurierte
Zustand, alle Daten zugreifen können und damit auch auf private
Attribute lesen und schreiben zugreifen.
Ja?
Hier haben wir ein Beispiel über ein Spiel, ein Rollenspiel, wo sie
den Spielzustand halten wollen.
Je näher die Spiele, sagen wir, an Stufe 13 kommen, desto eher
scheitern sie am Schwierigkeitsgrad und müssen dann von vorne
anfangen.
Ja?
Zum Laden und Speichern des Spielzustandes, dass sie nicht ganz von
vorne anfangen können, sondern auf Stufe 12 oder der vorigen Stufe.
Wie mache ich das?
Hier ist so ein Beispiel.
Spiel-Memento.
Spielstand ist gleich Spiel.
Punkt sicher ist Spielstand.
Das ist der Aufruf.
Das Spiel selbst.
Hier ist das Spiel.
Ich habe hier einen Spielstand nur mit einer einzigen Variable
angegeben.
Das können Sie sich vorstellen, dass bei einem ordentlichen Spiel
viel, viel mehr dazugehört.
Hier gibt es das sichere Spielstand und das restaurierte Spielstand.
Funktionen machen genau das.
Und hier das Spiel im Memento.
Das hat natürlich auch nur ein Attribut, Integer.
Mehr habe ich ja nicht an Daten und die zugehörigen Funktionen.
Dann kann ich hier oben also aufrufen, erstmal sichere Spielstand.
Und dann kann irgendwas kaputt gehen.
In meinem Spiel kann ich irgendwo versagen, etwas nicht schaffen.
Und dann kann ich Spielepunkt, restaurierte Spielstand aufrufen, indem
ich mir dieses Memento vornehme.
Ein spannenderes Beispiel ist, wenn ich sehr viele Mementos habe.
Nämlich, denken Sie an einen Editor und Sie wollen jeden einzelnen
Schritt, den Sie durchführen in Ihrem Editieren, in Ihren
Ediervorgang, sagen wir in Buchstaben einfügen, an Buchstaben lösen,
Buchstaben die Größe ändern, neu formatieren, irgendwelche Funktionen,
die Sie ausführen, da kann ich jedes Mal hergehen und als Teil der
Ausführung dieser neuen Methode erstmal sichern.
Immer das, was berührt wird in einer bestimmten Methode, erstmal den
Zustand sichern und diese gesicherten Zustände des Edierens in einer
Liste halten.
Ich kann jetzt viele, viele hundert Schritte enthalten, meistens sagt
man dann, man kann nicht mehr als zehn oder zwanzig Schritte
zurückgehen, aber nehmen wir einmal an, wir haben so eine Liste von
sicherten Zwischenzuständen, wenn jetzt der Benutzer sagt undo, da
habe ich die Daten da, wenn er nochmal sagt undo, habe ich die Daten
immer noch da und da kann ich also etwa zehn oder zwanzig Mal eine
Undo -Funktion aufrufen und aus diesen protokollartigen gespeicherten
Zuständen fast beliebig weit zurückgehen.
Ich kann dann auch wieder vorwärts gehen.
Ich kann auch sagen redo, wenn ich den angewendeten
Rückkehrmachenschritt, nämlich das Memento auslesen und wieder
überschreiben, wenn ich mir, bevor ich das mache, meinen Redo-Daten
speichere, bevor das Memento das Ursprungsobjekt überschreibt.
Also auch das können wir machen, beliebig langes undo, redo
programmieren.
Es ist klar, dass jetzt, wenn in diesem Spielzustand, hier haben wir
einen ganz normalen primitiven Datentyp, den kopieren wir einfach.
Wenn hier ein Verweis stünde auf ein anderes Objekt, dann müsste ich
natürlich auch dieses Objekt speichern, denn ich weiß nicht von
vornherein, ob dieses Objekt dann nicht später über einen anderen Weg
verändert würde.
Also muss ich die ganzen Variablen kopieren.
Und wie gesagt, je nach Größe dieses Zustands kann das viel oder wenig
sein.
Irgendwelche Fragen?
Ich hoffe, Sie haben verstanden, was das undo, redo geht.
Das geht genau mit so einem Memento.
Und natürlich entsprechend tiefen Kopien.
So, hier ist noch ein Beispiel, aber das können Sie selbst lesen.
Wenn Sie also an der Momentaufnahme eines Teils des Zustands eines
Objektes zwischenspeichern, werden muss, sodass es zu einem späteren
Zeitpunkt in diesen Zustand zurückversetzt werden kann.
Das ist genau die Funktion des Mementos.
Und ferner, wenn eine direkte Schnittstelle zum Ermitteln des
Zustandes der Implementierung, wenn ich irgendwie sage, ich will
einfach alle Attribute darin zwischenspeichern, das heißt ja, dass Sie
die gesamte Schnittstelle offenlegen, das wissen wir, ist nach dem
Geheimnisprinzip nicht wünschenswert, dann können wir mit diesem
Muster das ganz leicht erreichen, indem wir einfach kapieren, warum
das Sichern und das Wiederherstellen zum Objekt gehört und nicht zum
Memento.
Okay, das ist also ganz einfach.
Nächster Muster könnte ich fast überspringen, weil nämlich das
Prototyp Herstellen, wenn ich also Objekte erzeuge, kann ich
normalerweise einen Konstruktor hernehmen oder aber ich kann sagen, es
gibt schon so ein Objekt, das genauso ist, wie ich es brauche, ich
bräuchte nur eine Kopie davon, dazu gibt es, das nennt man dann den
Prototypen, also eine Kopie erzeugen, ist die Klonierfunktion und die
ist in Java schon eingebaut.
Also brauche ich erst gar nicht lange darüber reden.
Die Idee ist, dass ich konkrete Prototypen habe, die natürlich die
Klonfunktion haben und die sind mit dem Java-Objekt gegeben.
Und wenn ich mir eins speichern will, das ist fast genau das Gleiche
oder etwas ähnliches, wie das Memento vorher, nur dass ich jetzt nicht
rückversetzen will, sondern einfach nur die Kopie haben will.
Aber wir nehmen uns jetzt ein Beispiel her.
Nehmen wir an, wir haben eine Arraylist, das enthält ein Datum oder
viele Datum, das heißt AL und eine Arraylist AL2 und das AL existiere
schon, aber wir machen jetzt eine Klonierfunktion, rufen wir auf an
dem AL und weisen das AL2 zu.
Das sieht so aus, als wäre das eigentlich schon fertig.
Aber Java alleine reicht das nicht, denn Java erzeugt eine sogenannte
seichte Kopie.
Das heißt, wenn dieses AL2 gebaut wird, wird die Liste kopiert, aber
nicht die Objekte, auf die die Liste zeigt.
Daher brauche ich noch eine zweite Funktion, die die einzelnen Objekte
auch noch kopiert.
Genau genommen wird hier nur der Beginn der Liste kopiert, wenn mich
das Objekt auf das AL zeigt und ich denke, ich hätte eine kopierte
Liste, habe ich aber gar nicht, sondern ich muss noch einzelne Objekte
holen und zu dem Ding hinzufügen.
Das merken wir uns also, dass Clone in Java eine sogenannte seichte
Kopie erstellt.
Was immer Sie ein Objekt haben, was immer dieses Objekt weiter
verweist, wird nicht kopiert.
Wenn Sie das wollen, müssen Sie sich eine eigene Klonierfunktion
schreiben, was kein Problem wäre.
Die wäre in der Regel rekursiv, denn Sie wissen nicht, wohin Sie noch
überall hinkommen.
Und damit die ganzen Referenzen auch kopiert werden.
Das heißt, im Falle von vorhin müssen also alle Elemente von Hand in
die neue Liste kopiert werden, weil die Original-Klonierfunktion in
Java eben so wenig tut.
Kopiert genau ein Objekt.
Aber wozu das alles?
Brauche ich das überhaupt?
Wozu brauche ich diese Kopiererei?
Nun ja, es kann vorkommen, dass Sie große Objekte haben, deren Aufbau
mit dem Konstruktor mehr Zeit erfordert, als eigentlich das simple
Kopieren eines vorhandenen Objektes.
Sie können sich gewissermaßen ein Muster vorgeben, den Prototypen, und
der Konstruktor liefert einfach eine Kopie von diesem Muster, von dem
Prototypen.
Auch es kann sein, dass Sie bestimmte Objekte brauchen, aber deren
Klassen erst zur Laufzeit bestimmt werden.
Da ist es auch ganz praktisch, wenn Sie sagen, ja, jetzt weiß ich, was
ich haben will.
Hier ist der Prototyp davon, den kopiere ich mir.
Da muss ich unter Umständen gar nicht wissen, welche konkrete Klasse
das ist.
Oft ist es dann auch so, dass man ein Klassenarchiv von Fabriken
braucht, um die Klassen der Produkte, die man hat, Unterklassen der
Produkte nachzuempfinden.
Abstrakte Fabriken kennen Sie ja schon.
Da gibt es dann natürlich auch Unterklassenbildungen dieser Fabriken,
abstrakten Fabriken, und das möchte man manchmal vermeiden, weil man
einfach zu viele Unterklassen bildet.
Wenn es zwar so ist, dass die Klasse zwar eine Menge von Attributen
hat, aber eigentlich nur wenige Kopien, wirklich wenige Instanzen,
aber es sind wirklich vorkommen bestimmten Kombinationen von Werten,
dann legen Sie sich diese alle an, sagen wir 30 oder 100, und lesen
die einfach nur noch ab, indem Sie Prototypen kopieren.
Also, einfach.
Sie erinnern sich, dass Clonieren, dass Javaclone eben seichte Kopien
erzeugt, und keine Tiefen.
Wenn man die haben will, muss man das Clonieren überschreiben.
So, das nächste wäre der Zustand.
Den Zustand überspringen wir aber, weil wir den besser in der
Implementierung besprechen.
Das ist ein etwas verwickeltes Zustandsmuster, da werden wir es
genauer angeben, das lassen wir jetzt komplett aus.
Es wird auch nur wenig darüber gesagt, das hilft ja nicht viel.
Also lassen Sie sich bitte Zeit, bis wir zur Implementierung kommen.
Als nächstes betrachten wir die Steuerungsmuster.
Die Steuerungsmuster sind diejenigen, die bestimmen, was in welcher
Reihenfolge ausgeführt wird.
Wann eine bestimmte Operation ausgeführt wird.
Sagen Sie, das weiß ich doch, das schreibe ich in mein Programm rein.
Sage ich, nein, das wissen Sie nicht unbedingt, denn stellen Sie sich
vor, Sie haben ein interaktives Programm mit vielen Schaltflächen, und
welche Operation ausgeführt wird, das ist das, wo der Benutzer auf die
Schaltfläche klickt, und das können Sie nicht bestimmen.
Zu welchem Zeitpunkt Sie z.B.
auf sichern oder auf kursiv oder auf einfügen oder auf löschen oder
sonst etwas drücken, das weiß nur der Benutzer.
Das ist gerade bei interaktiven Programmen so, da braucht man also ein
Steuerungsmechanismus, der auf diese Wahlfreiheit der Bedienung
reagieren kann.
Da ist ein Muster insbesondere dabei, das nennen wir den Befehl.
Das Befehlsmuster ist das erste davon.
Es gibt auch hier in dieser Gruppe noch sehr viel mehr, als wir
behandeln werden, aber die hier reichen uns erstmal.
Und zwar sagen wir jetzt, wir benutzen ein Objekt, das wir zu einem
bestimmten Zeitpunkt konfigurieren und erzeugen, aber das eigentlich
eine Funktion ausführen soll.
Zu einem Zeitpunkt, den wir nicht vorhersagen können.
Und wir speichern dieses Objekt.
Zum Beispiel können wir das gebrauchen für Schaltflächen,
Menüeinträge, Knöpfe und so etwas.
Wir verknüpfen einen Befehl mit einer Schaltfläche, wenn auf diese
Schaltfläche geklickt wird, dann führen wir diesen Befehl aus.
Wie geht das?
Und das zeige ich Ihnen jetzt.
Nennt man übrigens auch Command natürlich, im Kommando Aktion oder
Transaktion.
Ist von der Struktur her auch einfach.
In der Verwendung manchmal etwas, wie Sie gleich sehen werden, etwas
involvierter.
Was wir brauchen ist, wir fangen immer an mit einer allgemeinen
Oberklasse, einer Schnittstelle, die hat in den meisten Fällen genau
einen Befehl, das nennen wir jetzt mal Führer aus.
Wir können es auch Do it nennen oder Run oder Execute.
Das ist immer das Gleiche.
Das ist das, was wir eigentlich ausgeführt haben wollen.
Dann haben wir einen konkreten Befehl.
Der kann sein, dass der sich seinen eigenen Zustand, seine eigenen
Attribute speichert, an denen er nämlich arbeitet, die Führer aus
Funktion.
Und es kann sein, dass wir auch noch einen Empfänger haben, sodass
nämlich der konkrete Befehl auf etwas angewendet werden muss.
Wir haben also Attribute drin und möglicherweise Assoziationen zu
einem Empfänger oder mehreren.
Und dann sagen wir, wir machen hier einen ganz einfachen Befehl,
nämlich der einfach nur sagt, was immer es ist, ich delegiere das an
den Empfänger.
Muss aber nicht so sein.
Wir implementieren also hier diese Operation.
Wie gesagt, greift hier auf Empfänger zu und ruft auf dem hier die
Aktion auf, was immer es ist, macht noch zusätzliche Dinge, ändert das
Erscheinungsbild des Knopfes, keine Ahnung, was noch alles zu tun ist,
also nicht verwirren, da kann beliebig viel drinstecken.
Aber das Interessante ist, dass wir jetzt festgelegt haben, wie etwas
aufgerufen wird.
Alles, was wir jetzt brauchen, ist, wir können jetzt hergehen und
lange Listen von diesen konkreten Befehlen machen, zum Beispiel, dann
durch diese Liste durchgehen und jeweils das Führerausdauern aufrufen
zu einem späteren Zeitpunkt.
Erst machen wir die Befehle, stellen sie in eine Liste.
Wir könnten zum Beispiel das Redo-Undo so implementieren, dass wir das
Redo -Undo erstens mit Mementos implementieren, aber auch per Befehl.
Ein Redo-Befehl gibt und ein Undo-Befehl.
Und wenn Sie jetzt eben über die Zeit hinweg ein ganzes Logbuch von
Einträgen haben, für Dinge, die man umkehren möchte, dann führen Sie
die aus und dann sagt irgendeiner so und jetzt drücke ich auf den
Befehl, umkehren, undo, rückgängig machen.
Dann sage ich, okay, was ist das letzte Element in meiner
Befehlskette?
Und da führe ich einfach den Befehl Führerausdauern auf und der tut
hoffentlich das Richtige.
Wenn er sagt, jetzt nochmal, noch ein Schritt, noch ein Schritt, noch
ein Schritt, dann wiederhole ich die Geschichte einfach.
Schauen wir uns ein kompliziertes Beispiel an.
Wieder eine interaktive Anwendung, dieses Mal mit Menüs.
Also hier ist einiges aufgebaut.
Hier steht meinetwegen die Anwendung.
Die hat ein Menü.
Die hat übrigens einen Befehl dabei, die sagt, füge hinzu.
Nämlich einen Menüeintrag hinzufügen, aber das Menü selbst besteht
jetzt aus mehreren Menüeinträgen.
Da frage ich kein Menü.
Und dann, so ein Menüeintrag besteht jetzt auch noch aus Befehlen.
Aus einem oder mehreren oder gar keinem natürlich.
Hier hat ein Beführer aus.
Und hier ist zum Beispiel der Einfügebefehl.
Irgendetwas einzufügen.
Der hat natürlich die Funktion Führeraus und die macht nichts weiter
als im Dokument die Füge-Ein-Operation auszuführen.
Die haben wir natürlich hier.
So, und jetzt weiß ich, wie ich die Menüs konfigurieren muss.
Ich muss immer nur einen geeigneten Befehl hinzufügen.
Zum Beispiel den Einfügebefehl, den Löschebefehl, den Sichern-Alles
-Befehl und so weiter.
Die hänge ich alle an die Menüs dran.
Braucht dann natürlich unterschiedliche Implementierung der
Ausführenfunktion.
Aber die Menübedienung bleibt die gleiche.
Jedes Mal, wenn auf eine Schaltfläche gedrückt wird, auf einen Menü
-Eintrag zum Beispiel, dann weiß ich, ich muss da nachschauen, was der
Befehl ist, der an dem Menü dranhängt und bei dem Führeraus aufrufen.
Und wenn das Ding richtig aufgebaut wird, dass das Einfügebefehl dazu
konfiguriert ist, dann läuft die Geschichte richtig ab.
Okay, also es ist schon das gleiche wie vorher, nur sind ein paar
Dinge davor geschaltet.
Und als Beispiel in diesem Fall die Ausführung von Schaltflächen.
Reaktion auf Schaltflächen mit Befehlen.
Gut, hier ist noch so ein Befehl, Ausführenbefehl wieder, hat
natürlich die Funktion Führeraus.
Die Funktion, das ist der Öffnenbefehl, der hat jetzt etwas mehr zu
tun, nämlich der sagt, frage den Benutzer nach dem neuen Namen des
neuen Dokuments, das hinzugefügt werden soll.
Erzeuge ein neues Dokument, das New Document, das kriege ich hier aus,
aus dem Konstruktor.
Dann rufe ich bei der Anwendung Füge hinzu aus, nämlich dieses
Dokument.
Da gibt es die Funktion da drüben.
Und am Schluss öffne ich das Dokument auch noch.
Das ist also ein etwas längerer Befehl, der aus mehreren Schritten
zusammengesetzt ist.
Aber ansonsten das Gleiche.
Was kann man noch?
Ach ja, wenn ich einzelne Befehle habe, dann ist es manchmal auch ganz
gut, Reihen von Befehlen zusammenzuführen.
Sie haben schon diese ganzen Befehle, nämlich vielleicht haben Sie
schon die Funktion neues Dokument anlegen, den Befehl Anwendung
hinzufügen Befehl und das Öffnen Befehl, die gibt es eigentlich schon.
Die hängen nämlich an einzelnen Menüeinträgen schon dran, haben Sie
schon längst.
Sollen Sie das jetzt nochmal schreiben oder machen Sie lieber einen
Makrobefehl?
Ein Makrobefehl sieht so aus, dass er sagt, ich bestehe aus mehreren
Befehlen und die Funktion führe aus hier tut nichts anderes als die
anhängenden Befehle herzunehmen für alle Befehle, das ist meine
Assoziation, führe aus.
Welches Muster erinnern Sie sich an das hier?
Ein Muster, das wir schon gesehen haben.
Denken Sie an Zusammensätzen, Aggregat.
Natürlich, das ist Kompositum.
Nichts weiter.
Sie müssen sich dieses Bild einprägen.
Oberklasse, Unterklassen und Assoziation nach oben, eben mit der
Aggregation.
Das ist Kompositum angewandt auf Befehle.
Man kann auch Befehle zusammenfassen.
Anwendbarkeit, wie immer, wenn Objekte mit einer auszuführenden Aktion
parametrisiert werden sollen, bei den Menü-Einträgen.
Wenn Anfragen zu unterschiedlichen Zeiten spezifiziert, aufgereiht und
ausgeführt werden sollen.
Wenn Sie rückgängig machen, von Operationen haben wollen.
Undo, da brauche ich auch noch das Memento dazu.
Wenn das Mitprotokollieren von Änderungen unterstützt werden soll, da
kann ich nämlich folgendes machen.
Ich sage, anstelle eines Undos, habe ich jetzt eine andere Situation,
die sagt, ich lege erstmal eine Sicherungskopie meiner gesamten
Anwendung ab und arbeite dann weiter.
Und sollte meine Anwendung abstürzen, dann würde ich gerne auf die
Sicherungskopie zurückgehen.
Dann brauchen Sie jetzt so etwas ähnliches, nämlich Sie wollen dann
natürlich, wenn Sie die Sicherungskopie wieder restauriert haben, dann
sagen Sie, ich habe ja aber noch eine halbe Stunde gearbeitet, wo sind
denn alle diese Befehle, die ich da ausgeführt habe?
Naja, die schreiben Sie sich weg aus dem Protokoll der Befehle, die
Sie ausgeführt haben.
Also nochmal, die Situation ist folgendermaßen.
Sie haben eine Anwendung, von der Sie nicht sicher sind, dass sie
nicht irgendwann mal abstürzt.
Kennen Sie sicher schon.
Und in diesem Fall möchten Sie möglichst wenig Arbeit wiederholen
müssen.
Also sagen Sie, gut, ich sichere jetzt erstmal die ganze Anwendung,
wie immer dieses Sichern geht, das natürlich auf einem stabilen
Hintergrund speichert, denn sonst ist es wertlos, wenn Ihr Programm
abschmiert.
Und dann sagen Sie, und außerdem sichere ich mir von diesem Zeitpunkt
an alle Befehle, die ich ausführe.
Ich führe sie aus und speichere sie mir mit den zugehörigen
Parametern, wenn da welche notwendig waren.
Wenn mir das System abbricht, dann sage ich, gut, alte Version wieder
hervorholen und jetzt die gesicherten Befehle ausführend der Reihe
nach.
Dann verliere ich vielleicht einen, den allerletzten Befehl.
Den führe ich dann aus und hoffentlich funktioniert er dann.
Es kann natürlich sein, dass genau der gleiche Fehler nochmal auftritt
und dann hilft Ihnen das nichts.
Das heißt, dann müssen Sie vielleicht an einer gewissen Stelle in
dieser Vorwärtsbewegung ab dem Sicherungszeitpunkt einen anderen Weg
einschlagen.
Okay, also undo, redo und Sicherungskopien oder Sicherungskopien
vorwärtsarbeiten.
Das wäre das Mitprotokollieren und dann natürlich auch noch die
Situation, dass Sie viele einfache Befehle haben, die Sie aber auch
gerne zusammengesetzt hätten und die Sie nicht erneut schreiben
wollen.
Und erneut schreiben ist natürlich immer böse für uns
Softwareprogrammierer.
Also Programmieren durch Kopieren von Code ist böse, denn es erzeugt
Kopien, die einen unnötigen zusätzlichen Wartungsaufwand erzeugen.
Wir müssen dann feststellen, wenn wir ein Stück Code korrigieren
müssen, weil es einen Fehler enthält, müssen wir alle Kopien finden
können, um die Korrektur nachzuziehen.
Und das wollen wir auf keinen Fall machen.
Deswegen sagen wir, da machen wir uns lieber einen zusammengesetzten
Befehl, der die vorhandene Logik, die es gibt, einfach nur der Reihe
nach aufnimmt.
Also einer dieser Befehle korrigiert werden muss.
Dann muss ich nicht alle Stellen finden, an denen es noch vorkommen
könnte.
Ich muss nur den einzelnen Befehl reparieren.
Okay, gut.
Fragen dazu?
Das ist der Auftraggeber-Auftragnehmer oder Master-Worker.
Früher hat man dazu auch Master-Slave gesagt.
Das ist heute politisch nicht mehr akzeptabel.
Also sagt man Master-Worker oder auch Auftraggeber-Nehmer-Muster.
Eigentlich besteht der Auftraggeber-Auftragnehmer-Situation aus ganz
was Einfachem.
Es gibt einen Auftraggeber oder den Master, der verteilt Arbeit an die
Arbeiter.
Und die Arbeiter sind einzelne Prozesse.
Das hat nämlich keinen Sinn.
Dieses Muster rentiert sich nur, wenn sie ein paralleles Programm
schreiben.
Dass nämlich diese Arbeitnehmer oder Arbeiter parallel laufen.
Dann kann nämlich der Master hergehen, denen eine Arbeit schicken, ein
Arbeitspaket, ein Abschnitt in einem großen Feld zu bearbeiten und dem
nächsten Arbeitgeber einen weiteren Abschnitt und dem übernächsten
noch einen Abschnitt.
Übrigens ein beliebtes Muster bei der Parallelverarbeitung.
Und diese Arbeiter können dann natürlich parallel arbeiten.
Das Schicken der Aufgaben muss natürlich ganz kurz gehen und nicht so
lange dauern, wie das eigentliche Bearbeiten, denn sonst könnte der
Master das selbst machen.
Was der Master auch noch machen kann, er kann am Schluss, wenn nur
noch eine Aufgabe übrig ist, diese Aufgabe selbst übernehmen.
Dann habe ich für einen Augenblick auch noch eine vierfache
Parallelität und nicht nur die dreifache Parallelität.
Wo könnte ich denn sowas benutzen?
Nun, man könnte sowas benutzen beim Bearbeiten eines Bildes.
Zum Beispiel, wenn Sie aus einer Überstrahlverfolgung oder Raytracing
eines dieser fotorealistischen Bilder erzeugen wollen.
Das ist ziemlich viel Rechenarbeit.
Da haben Sie nämlich ein 3D-Modell im Rechner, das sagt, ich habe hier
einen Berg und einen Himmel und eine Sonne und im Vordergrund
vielleicht irgendwelche Häuser und so weiter.
Dafür haben Sie alles 3D-Modelle, die deren Struktur angeben.
Und jetzt sagen Sie, um das Bild zu erzeugen, schieße ich einen Strahl
in diese Szene hinein, durch dieses Fenster hindurch, bis es auf ein
Objekt auftrifft, dieser Strahl.
Das wird also im Rechner simuliert und sagt, an dieser Oberfläche, wie
reflektiert die und woher bekommt die ihr Licht?
Das heißt, ich muss die Strahlen dann gewissermaßen rückwärts
verfolgen.
Das eignet sich übrigens besonders gut für spiegelnde Flächen.
Für diffuse Flächen braucht man ein etwas anderes Verfahren, aber für
spiegelnde Flächen ist das Verfahren hervorragend.
Es gibt wirklich sehr tolle Bilder dafür.
Aber die Rechnerarbeit ist enorm.
Was mache ich da?
Na gut, ich nehme mir dieses Zielbild auf in verschiedene Kacheln, in
diesem Fall sind sie alle gleich groß, und lasse den Master darauf
losarbeiten und sage, gut, ich habe drei Arbeiter, die kriegen also
die ersten drei Bilder und wenn einer sagt, ich bin jetzt fertig, dann
werden die das in dem Zielbild speichern und dann schicke ich ihm mehr
Arbeit.
Und dann kann es auch vorkommen, dass zum Beispiel der Himmel hier
ganz schnell berechnet ist, weil da keinerlei Objekte oben sind,
während hier unten vielleicht die Rechnung etwas länger dauert, da
können andere Objekte reinkommen, die unterschiedlich gerichtete
Oberflächen haben und damit muss da sehr viel mehr gerechnet werden.
Dann kann es also sein, dass der hier lange rechnet, während der hier
schon fertig ist.
Dann ist es okay, wenn ich dem hier sage, okay, du kriegst jetzt schon
die nächste Arbeit und der rechnet immer noch, vielleicht ist er dann
schon fertig mit dem, dann kriegt er halt noch eine Aufgabe, obwohl
der hier immer noch rechnet.
Damit schaffe ich es auch, wenn der endlich fertig ist, dann kriegt er
halt das nächste freie Stück.
Damit schaffe ich es auch, wenn die Berechnung der einzelnen Kacheln
unterschiedlich lang dauert, dass ein bestimmter Ausgleich entsteht.
Manche arbeiten eben länger und die, die kürzer weniger Arbeit zu tun
haben, die kriegen halt mehr Aufgaben.
Und am Schluss muss ich warten, bis auch der Letzte mit seiner Aufgabe
fertig ist.
Dieser Ausgleich der Arbeit funktioniert umso besser, je kleiner die
Arbeitspakete sind.
Das kann man gleich sehen, denn dann ist am Schluss der Auslauf nicht
mehr so ausgedehnt.
Wenn zum Beispiel der hier noch sehr viel Arbeit hätte, weil hier sehr
viele Details sitzen, dann dauert der natürlich länger als der
Allerletzte.
Das ist die Idee.
Und hier am Beispiel der Strahlverfolgung, es gibt jede Menge von
anderen Möglichkeiten, zum Beispiel SETI at Home, da wird nach
außerterrestrialer Intelligenz geforscht.
Da können Sie sich selbst beteiligen, können Ihren PC dafür zur
Verfügung stellen, dass in Radar und sonstigen Radiosignalen aus dem
Weltall untersucht wird, ob es da irgendwo Leben gibt.
Holding at Home, da können Sie mithelfen, die 3D-Struktur von
Proteinen zu berechnen.
Die 3D-Struktur von Proteinen sind sehr komplizierte Faltungen, die
man aufgrund der molekularen Kräfte ausrechnet.
Und da kriegen Sie auch ein Stück Protein, das können Sie dann rechnen
und dann zurückgeben.
Es gibt Leute, die suchen Primzahlen, sogenannte Mersenne Primes.
Das sind Primzahlen, die sind 2 hoch n minus 1.
2 hoch n ist natürlich eine gerade Zahl, ist sicher keine Primzahl,
aber eine Zahl 2 hoch 953.526 minus 1.
Ist das eine Primzahl?
Genau, da gibt es auch, ja, nein, die Primzahlen sind dann schon sehr,
sehr schwierig da drin und können Sie sich auch beteiligen, Mersenne
Primes, Ihren Rechner zur Verfügung stellen.
Das ist genau Master Worker, da sitzt irgendjemand und er schickt
Ihnen ein Paket von Primzahlen, die Sie durchprobieren müssen.
Entschuldigung, keine Primzahlen, ein Paket von Zahlen, ein Bereich
von Zahlen, in diesem Bereich Potenzen, die Sie dann abprüfen müssen,
ob sich die Dinge teilen lassen.
Wenn Sie einen Teil gefunden haben, sind Sie schon fertig.
Wenn Sie keinen Teil finden, dann haben Sie eine Primzahl.
Dann kriegen Sie einen Preis.
Also, es gibt viele Möglichkeiten.
Das zu machen ist eigentlich sehr intuitiv.
Eine Mannschaft zusammenstellen und dann Arbeiten lassen.
Fragen dazu?
Also, wenn es mehrere Aufgaben gibt, die unabhängig voneinander
berechnet werden können, wenn da Abhängigkeiten bestehen, dann bringt
dieses Master-Ding nichts mehr, denn dann müssen Sie einzeln
aufeinander warten.
Und natürlich, wenn mehrere Prozessoren zur Verfügung stehen, entweder
Sie haben in Ihrem Rechner selbst einen Mehrkernprozessor, was sehr
wahrscheinlich ist, oder aber Sie können mehrere Rechner in einem Netz
zusammenschalten, die zusammenarbeiten.
Und schließlich, wenn die Belastung der einzelnen Arbeiter
ausgeglichen werden soll, wenn Sie also keine statische, festgelegte
Aufgabe sagen, Sie könnten das Bild vorhin genommen haben, einfach in
drei geteilt haben, drei Streifen und jedem drei gegeben zu haben,
dann kann es sein, dass einer schnell fertig wird und der andere sehr
viel zu rechnen hat.
Dann nehmen Sie besser dieses Muster her, in feinere Abteilungen
aufteilen und die Arbeiter mehrfach arbeiten lassen.
Das war schon der Master Worker.
Sie wissen jetzt natürlich noch nicht, wie Sie so Paralleldinger
erzeugen.
Da gibt es in Java Vorkehrungen dafür, die heißen Threads oder Thäden.
Die können Sie erzeugen, beliebig viele.
Es orientiert sich nicht immer, beliebig viele zu erzeugen, sondern
meistens nur so etwa doppelt so viele, wie Sie Prozessoren haben.
Ihnen kann man Aufgaben zuweisen.
Wie das geht, werden Sie nachher sehen, werden wir in der
Implementierung des Kapitels sehen, wie man das programmiert.
Und am Schluss muss man natürlich warten auf alle drei, dass sie alle
fertig sind.
Dann gibt es eine Methode, die Sie aufrufen können in dem Hauptfaden
des Masters.
Der ruft einfach die Methode Join an all diesen Workern auf.
Dann wartet die Methode Join, bis ein Arbeiter mit seiner Aufgabe, mit
seiner Berechnung durch ist.
Und schon geht die Geschichte weiter.
Also gut, Bequemlichkeitsmuster, da bin ich jetzt so bequem, dass ich
Sie die selbst studieren lasse, sind sehr einfach.
Wir gehen lieber gleich auf das Kapitel Implementierung.
Also, ist wirklich einfach.
Aber das können Sie sich selbst anschauen.
Da geht es nur darum, dass Sie sagen, ich habe eine Methode, die hat
eigentlich 27 Parameter, aber in vielen Fällen möchte ich eigentlich
nur einen Parameter angeben oder keinen.
Dann brauche ich Standardwerte für die übrigen Parameter.
Dann macht das Bequemlichkeitsmuster.
Ich überlade hier die Methodennamen.
Einmal mit drei Parametern, dann mit zwei, dann mit keinen.
Fassade haben wir schon mehrmals besprochen.
Hier taucht die nochmal auf, das ist auch noch ein Beispiel.
Und am Schluss ein Witz, ein Witz für sich eigentlich.
Das Nullobjekt habe ich noch nie gebraucht, aber es ist ganz nett.
Das dürfen Sie selbst durchstudieren, wie das funktioniert.
Wo finde ich mehr über Enthofsmuster?
Also, das Standardwerk, an dem wir uns zu großen Teilen gehalten
haben, ist das Buch Design Patterns, hat noch einen Untertitel, den
ich jetzt vergesse.
Edison-Wesley 95 ist in unserer Bibliothek.
Es gibt auch eine deutsche Ausgabe Enthofsmuster, hervorragende
Übersetzung, was man nicht immer sagen kann bei Übersetzungen, aber
das hier, Kollege Riehle hat das sehr gut übersetzt.
Headfirst kennen Sie wahrscheinlich schon, haben Sie sich
wahrscheinlich schon angeschaut, heruntergeladen oder in der
Bibliothek angeschaut.
Enthofsmuster von Kopf bis Fuß heißt das übrigens auf Deutsch.
Da gibt es auch eine gute deutsche Übersetzung.
Und die anderen brauchen Sie jetzt nicht mehr sich anzuschauen, aber
wenn Sie sich wirklich interessiert in Enthofsmuster, dann gibt es
sogar eine Konferenzreihe, Pattern Languages of Program Design.
Ich habe aufgehört mitzuzählen, irgendwann mal.
Dann gibt es ein Muster Pattern Hatching.
Das ist so ein Buch, das man mal im Zug liest oder so.
Das ist ein Buch, ich habe es schon erwähnt, das nur darüber spricht,
welche Enthofsmuster, die man am besten einsetzt, um eine Datei
-Hierarchie zu bauen.
In allen Variationen.
Das ist ein bisschen viel, aber ich hätte nie gedacht, dass man ein
ganzes Buch darüber schreiben kann, aber der hat es geschafft.
Wie John Blissett es.
Patterns in Java gibt es auch noch ein Buch.
Pattern Oriented Software Architecture und so weiter.
Es gibt auch Patterns in Java.
Es gibt ein weiteres Buch über Muster in Java.
Also es gibt genügend Literatur.
Uns ist das sehr wichtig.
Und als Alternative Headfirst Design Patterns.
Okay, was machen wir mit den Enthofsmustern?
Nochmal zurück, warum wir uns so interessiert haben dafür.
Sie bieten uns einmal ein Vokabular zur effizienten Kommunikation
zwischen Teammitgliedern.
Ich gebe Ihnen dazu eine kleine Geschichte, die ich selbst erlebt
habe.
Ich war auf einer Konferenz.
Da waren die Enthofsmuster noch jung und mir waren sie unbekannt.
Aber es gab schon eine kleine Gemeinde, die hat die Enthofsmuster
studiert und gesammelt.
Und ich hatte mal beobachtet, da kam also irgendwo ein Artikel raus,
den sie sich dann angeschaut haben in der Raufe dieser Konferenz.
Das waren also zwei Seiten UML-Diagramme.
Also ich dachte mir, muss ich mir das wirklich antun?
Jede einzelne Klasse, jede einzelne Methode durchdenken, jede
Vererbung.
Ach ja, das ist das und das ist das.
Und man scheut da gewissermaßen zurück, das ist so blöd wie
Fremdencode zu lesen.
UML-Diagramme, fremde UML-Diagramme.
Durchdringen ist manchmal schwierig.
Aber die, die gingen her und sagten, siehst du, der hat da das
Kompositum drin und da hat er den Adapter und da ist der Beobachter,
der über was redet der überhaupt?
Und in Nullkommanix hatten sie die zwei Seiten verstanden.
Und ich stand davor und sagte, Bahnhof.
Das hat mich überzeugt.
Sie erfassen auch den Stand der Kunst.
Wie mache ich es?
Wozu kann ich es gebrauchen?
Was sind die Variationen?
Denken Sie an den Beobachter.
Das sagten wir auch, wir brauchen Subjekt, Beobachter.
Wir brauchen eine Aktualisierungsschnittstelle und da gibt es die und
die Varianten.
Beim Kompositum hatten wir auch mehrere Varianten angesehen, bei den
heutigen wieder nicht.
Dokumentieren auch gut diese Entwürfe.
Da gibt es entsprechende Empfehlungen, dass man an die Klassen
reinschreibt, welche Muster verwendet wurden und welcher Bestandteil
des Musters es ist.
So langweilig heute?
Lieber nichts sagen, haben Sie recht.
Also gut, der Beobachter, wo war ich da gerade?
Beobachter würde man sagen, also Beobachter, Doppelpunkt, Subjekt.
Beobachter, Punkt, Beobachter.
Oder Kompositum, Oberklasse, Kompositum, Blatt, Kompositum, Aggregat.
Und die einzelnen Klassen so markiert.
Machen aus Anfängen, hoffentlich gute Entwerfer, machen gute Entwerfer
noch besser, verbessern Qualität und Produktivität und sind übrigens
schwierig zu empfinden.
Da gibt es übrigens Clubs, die sich formieren, wo die Leute sagen, wir
studieren unsere Software, unsere Firmensoftware und versuchen da ein
Schatz von Entwurfsmustern zu heben.
Glauben Sie bloß nicht, dass die Anzahl der Entwurfsmuster, die ich
Ihnen jetzt gegeben habe, alles ist, was es gibt.
Da gibt es noch viel, viel mehr.
Aber die werden, je größer sie sind, desto spezialisierter werden sie.
Und ich wollte Ihnen möglichst allgemein verwendbare Muster geben, die
Sie auch schon benutzen können.
Es gibt zum Beispiel interessante, ungefähr 50, 60 Stück, nur für
Parallelverarbeitung.
Wir werden ein bisschen Parallelverarbeitung machen, da werden wir ein
oder zwei dieser Muster sehen.
Aber wie gesagt, da gibt es sehr viel mehr und Sie werden im Laufe
Ihres Studiums hoffentlich noch mehr davon kennenlernen.
Oder auch, wenn Sie mal in die Arbeitswelt gehen, dort weitere Muster
kennenlernen, die ganz brauchbar sind.
Und jetzt noch eine andere Bemerkung.
Es hatte, glaube ich, schon mal gesagt, dass es in der Softwaretechnik
immer diese Moden gibt.
Und zunächst sah diese Entwurfsmustergeschichte auch sehr nach Mode
aus.
Außer dieses eine Erlebnis, das ich da hatte, wo die Leute hergingen
und einen Entwurf in Nullkommanichts verstanden hatten und ich nicht.
Die wissen was, das muss ich auch wissen.
Aber was wir dann gemacht haben, und zwar hier in Karlsruhe und an
anderen Stellen, wir haben dann Experimente durchgeführt, um zu
prüfen, ob die Entwurfster wirklich das tun, was sie versprechen.
Es ist einfach nur eine neue Mode, die durch die Stadt getrieben wird.
Jetzt sind wieder gerade Entwurfsmuster, toll, und dann ist es was
anderes.
Dann ist es Cloud und dann ist es Big Data und dann ist es dies und
das.
Und dann ist es Scrum und dann ist es agile Methoden.
Es gibt ja jede Menge Moden und wir wollten einfach wissen, tun die
wirklich?
Und wir hatten dann also angefangen, empirisch zu prüfen, ob Muster
tatsächlich beim Warten von Programmen zum Beispiel helfen.
Wir hatten also die Situation, dass wir Teilnehmer hatten aus der
Industrie, die bekamen von mir einen Kurs in Entwurfsmuster, sowas wie
Sie jetzt gerade erlebt haben, mit Übungen und die konnten danach in
einem Experiment teilnehmen.
Und damit wir etwas zu vergleichen hatten, gaben wir ihnen die
gleichen Aufgaben vorher.
Wir hatten einen Satz von vier Aufgaben und einen weiteren Satz von
vier Aufgaben, die dann vorher normal gelöst werden mussten und im
zweiten Durchgang mit Entwurfsmustern.
In beiden Fällen gaben wir die Entwurfsmuster an, aber die Teilnehmer
kannten sie noch nicht.
Dann konnten wir verstehen, ob diejenigen Leute, die den
Entwurfsmuster hatten und kannten, die Wartungsarbeit schneller
erledigen konnten.
Oder mit besserer Qualität.
Weniger Fehler.
Sie wissen ja, man kann jede Aufgabe furchtbar schnell lösen.
Man löst den Anführungszeichen, den man missliefert.
Also da gibt es eine Interaktion zwischen Qualität und die Zeit, die
man darauf verwendet.
Und wir konnten also feststellen, dass dann entweder die Leute
schneller sind und die richtigen Lösungen liefern oder zwar nicht
schneller, aber trotzdem die bessere Qualität liefern, als die, die
ohne Muster arbeiten mussten.
Da hatten wir also eine Reihe von drei oder vier Experimenten
durchgeführt, die alles bestätigen, was ich Ihnen hier gesagt habe.
Die Leute schneller werden, bessere Qualität liefern, dass sie sich
besser verständigen können.
Auch dafür hatten wir ein Experiment gemacht, das ging ganz anders.
Das ging über laut sprechen beim Entwerfen, Protokollieren, Codieren.
Also ein richtig soziologisches Experiment.
Da hatten wir zum Beispiel festgestellt, dass vor den Entwurfsmustern,
wir hatten bei den Teilnehmern so einen richtigen Quatscher, der alle
tot quatscht.
Den einen oder anderen Zeug kennen Sie ja, der aber keine Ahnung hat.
Aber der andere im Team, der eigentlich der bessere Programmierer war,
konnte sich gegen den nicht durchsetzen.
Und dann das gleiche Paar ging dann durch den Entwurfsmustertraining
hindurch und mussten ähnliche Aufgaben erneut lösen.
Und siehe da, plötzlich ist der schüchterne, leise, aber eigentlich
bessere.
Sieht gleich mit dem, der eigentlich vorher keine Ahnung hatte und sie
erreichen ein Niveau, wo sie sich wesentlich besser über einen Entwurf
austauschen konnten als vorher.
Auch ganz interessant.
Also ich erzähle Ihnen hier keine Mode, sondern etwas, was
nachweislich gut funktioniert.
Ich wäre froh, wenn ich das für alles, was ich Ihnen sagen kann, was
ich Ihnen hier erzähle, auch nachweisen könnte, aber das geht leider
noch nicht so durchgängig hin, wie ich es gerne hätte.
So, damit wären wir also mit dem Entwurfskapitel an sich fertig.
Sie erinnern sich, wir hatten begonnen mit dem Geheimnisprinzip, dem
Information -Writing-Prinzip.
Wir hatten dann das Modularisierungsprinzip von Parnas und hatten
einfach da gesagt, da muss man bestimmte Kriterien erfüllen, nämlich
Entwurfsdetails als Geheimnisse in einem Modul kapseln.
Aber es sagte niemand, wie das geht.
Mit den Entwurfsmustern wissen Sie jetzt in vielen Fällen, gerade bei
den Entkopplungsmustern, wie das geht.
Was können Sie verstecken, was sollten Sie nicht verstecken, welche
Schnittstellen brauchen Sie.
Denken Sie an das Kompositum, den Beobachter, den Besucher, das sind
alles diese Fälle.
Memento ist genauso so ein Fall.
Memento sagt, ich brauche nicht offen zu legen die gesamten Attribute,
um ein Objekt zu retten.
Indem ich einfach die Rette-Funktion, Sicherungsfunktion in die Klasse
des Objektes mit aufnehme.
Jetzt kommen wir zur Implementierungsphase.
Jetzt kommt ein bisschen Vorspann, den können wir relativ rasch
übergehen.
Die Implementierungsphase.
Wo sind wir jetzt?
Hier, im blauen Bereich sind wir jetzt.
Wir haben hier den Entwurf durchgeführt, mit den Entwurfsmustern
hoffentlich.
Haben einen sauberen Entwurf gemacht und haben jetzt jede Menge UML
-Diagramme und stehen vor der Aufgabe, das Zeug in lauffähige
Programme zu übersetzen.
Per Hand.
Leider geht das noch nicht automatisch.
Und jetzt sind einige Dinge zu erklären.
Erstens, was kommt rein?
Das ist schon klar.
Was kommt hier raus?
Sie sehen, hier ist noch eine Testphase nachgeschaltet.
Aber wir werden auch bereits in der Implementierungsphase testen.
Nämlich die einzelnen Komponenten, die werden als Teil der
Implementierungsphase getestet.
Die Testphase ist für das Integrationstesten, wo man mehrere Pakete
zusammenpackt und gemeinsam testet.
Das ist also die Implementierung.
Natürlich habe ich jetzt nicht mehr so viel zu sagen, denn Nummer
eins, Sie haben die Programmiervorlesung schon hinter sich, können
eigentlich schon implementieren.
Sie haben außerdem die Algorithmentechnikvorlesung, da wissen Sie, wie
Sie jetzt komplexe Dinge lösen können, algorithmisch gesehen.
Und Sie haben die ganzen Puzzler gesehen.
Was soll ich Ihnen von der Implementierung noch erzählen?
Ein paar Kleinigkeiten hätte ich noch, aber in der Tat, ich will jetzt
nicht über eine Programmiersprache reden.
Lernziele, OMR in ablauffähigen Code umwandeln können, das ist schon
ein sehr eingeschränktes Problem.
Programmierrichtlinien anwenden und überprüfen können, kennen Sie auch
schon.
Checkstyle, selbstkontrolliertes Programmieren anwenden können, das
kennen Sie wahrscheinlich noch nicht.
Außerdem werden wir ein bisschen über Parallelität reden.
Nämlich Einführung und Überblick, Abbildung von OMR auf Code, das ist
ziemlich schnell abgehandelt.
Parallelität, da werden wir ein bisschen drüber reden müssen, das
kommt dann wahrscheinlich am Freitag.
Und die Programmierrichtlinien kann ich auch kurz fassen, denn Sie
kennen sie eigentlich schon.
Wir hören natürlich zur Implementierung dazu, zur Softwaretechnik
dazu, selbstkontrolliertes Programmieren, da geht es darum, wie man
sich selbst beobachtet und vermeidet, dass man immer die gleichen
Fehler macht.
Immer nicht merkt, wo man seinen Kommentar geschlossen hat, immer
nicht merkt, warum die Typen nicht stimmen.
Hier und da was vergisst, wenn man beim Feldzugriff irgendwelche
Fehler macht, das und und vergisst, statt dem und und so weiter.
Jede dieser Fehler sehr individuell herausfinden und mit der Zeit
vermeiden.
Also Programmieren, Dokumentieren und Testen der Systemkomponenten.
Aufgrund von vorgegebenen Spezifikationen der Systemkomponenten, das
sind in der Regel Umweltdiagramme plus Spezifikationen der Methoden.
Das machen wir hier.
Wir können ein Entwurf, jedenfalls abhängig von der Entwurfsmethode,
hier unten im datenobjektorientierten Entwurf wissen Sie, wie es geht.
Wir haben Schnittstellen und Klassen und Pakete, Menge von
interagierenden Klassen.
Wenn Sie im modularen Entwurf sind, sprich in C programmieren, haben
Sie leider keine Objekte.
Aber wir können dort auch einen objektorientierten Entwurf abbilden
und durchaus Umweltklassen nehmen und danach mit einer nicht
objektorientierten Programmiersprache weiterarbeiten.
Da muss ich nur ein paar Details zeigen, dann haben Sie das geschafft.
Aber es kann ja sein, dass Sie diese Module unterschiedlich aussehen,
es kann sein, dass Sie ein rein funktionales Modul haben, das heißt,
das sind einfach Methoden mit ein paar gemeinsamen Variablen
effektuell.
Das ist fast so wie eine Klasse.
Es kann aber auch sein, dass es nur Datenobjekte enthält.
Oder aber Sie haben ein Datentypmodul, da werden nur Typen erklärt,
von denen Sie Instanzen bilden können.
Das ist fast so zu sagen, als könnte ich hier drin so etwas wie
Klassen erzeugen, sogenannte Datentypen, das geht auch in C übrigens.
Und von diesen Typen kann ich dann, wo ich sie brauche, Instanzen
erzeugen.
Okay.
Gut, wir haben eine Spezifikation, das können wir jetzt überspringen.
Hier kommt noch ein bisschen Detail, was da eigentlich passiert.
Wir sind jetzt hier in der Implementierung, kommen aus einem Dorf, wir
kriegen rein die Softwarearchitektur in Form von UML oder
Modulführern, Spezifikationen der Systemkomponenten und liefern was?
Quellprogramme inklusive Dokumentation, Objektprogramme, das sind die
übersetzten Einheiten, das macht natürlich unser Compiler, Testplanung
und Testprotokoll beziehungsweise Verifikationsdokumentation, das
heißt Per-Unit-Installationen, die die Testplanung darstellen, die ich
dann zu jeder Zeit ausführen kann und natürlich schon einmal
ausgeführt habe.
Oder, falls wir akademisch orientiert sind oder Programme schreiben
müssen, die höchste Sicherheitsanforderungen erfüllen müssen, dann
kann es sein, dass wir Verifikationsdokumentation liefern müssen.
Das heißt, es gibt tatsächlich Methoden, die einen mathematischen
Beweis führen, ob ein Programm seiner Spezifikation genügt.
Hundertprozentig kommt in dieser Vorlesung nicht vor, aber dazu gibt
es Spezialvorlesungen, wenn Sie das interessiert.
Geht leider nur für relativ kleine Programme.
So, wird auch Programmierung im Kleinen erzeugt.
Ich gehe jetzt die einzelnen Punkte nicht nur durch, sondern will
jetzt gleich wirklich zur Implementierung kommen.
So, dann schauen wir uns einfach erstmal die UML-Abbildung an.
Nein, das war es nicht.
Das ist es.
Und jetzt die Präsentation schalten, nicht mehr fokussieren.
Und los geht's.
Also, das Material finden Sie übrigens wieder mal in Brücke.
Using UML Patterns in Java.
So, lesen.
Abbildung von Klassendiagrammen.
So, jetzt schauen wir uns erstmal an.
Sie haben sie schon ein bisschen gesehen.
Bei OO-Sprachen haben wir jetzt also sowas gegeben, wie wir haben eine
Klasse C, Attribute und Methoden.
Was mache ich jetzt?
Bei OO-Sprachen erscheint etwas nicht.
Moment.
Irgendwas ist verkehrt.
Naja, das kann ich Ihnen aber auch sagen.
Also, wenn Sie eine Klasse C haben, ist es einfach, wie Sie sie
hinschreiben.
UML -Diagramm, da schreiben Sie die Attribute und Methoden hin.
Aber wie mache ich das in C?
Nicht in OO-Sprachen zum Beispiel.
In C gibt es natürlich die Strukturen.
Das sind also zum Beispiel die Struktur C, die könnte jetzt meine
Klasse sein.
Die hat jetzt meinetwegen ein Attribut drin.
Aber nicht die Methoden.
Kann ich leider zu den Strukturen in C nicht hinzufügen.
Das Klassenkonzept gibt es da nicht.
Gibt es erst ab C++.
So, und wie kriege ich jetzt davon Instanzen, ohne die Methoden?
Naja, da gibt es zwei Methoden, das zu machen.
Nämlich, ich sage Struct C, das heißt, ich brauche wieder eine
Struktur C.
Und zwar drei Stück davon.
C1, C2, C3.
Einen Konstruktor gibt es auch nicht.
Das heißt, wenn Sie die Dinge initialisieren sollen, müssen Sie eine
spezielle Methode schreiben.
Eine Funktion hier, die die Dinge initialisiert.
Wenn Sie jetzt auf das A1 von C1 zugreifen wollen, ist das wie in
Java.
Sie schreiben C1 Punkt Attributname.
Auch einfach.
Es gibt aber noch eine andere und die geht, was ich vorhin gesagt
habe, wenn Sie Typdefinitionen haben.
Sie können in C auch das hier schreiben.
Sie können sagen, ich definiere mir einen neuen Typ.
Und der Typ heißt Groß C.
Und es ist von der Art Struktur, nämlich Attribute.
Sie listen einfach hier die Attribute auf.
Ich habe jetzt nur eins hingeschrieben.
Dazu bräuchte ich natürlich keine Struktur für ein einziges Attribut.
Nämlich halt das Attribut als Variable her.
Aber im Generellen wollen Sie natürlich mehrere Attribute haben.
Und jetzt ist die Notation auch wieder ähnlich wie in Java.
Um jetzt Instanzen zu erzeugen, schreiben Sie C, Groß C, den Namen des
Typs.
C1, C2, C3.
Das ist eigentlich die schlimmste Art und Weise, erst eine
Typdefinition zu schreiben.
Das ist wie eine Klassendeklaration.
Und dann sagen, von dieser Typdefinition drei Instanzen.
Nochmal zurück zum vorigen Beispiel.
Da ist ein bisschen mehr Schreibaufwand.
Da sagen Sie nur, ich habe eine Struktur, gebe das Muster vor und dann
sagen Sie, von dieser Struktur C möchte ich drei Instanzen.
Da müssen Sie eben noch Struct hinschreiben, während Sie das hier
nicht müssen.
Typdefinition stattdessen benutzen.
Eigentlich ganz praktisch.
Wenn Sie anlegen, zum Beispiel innerhalb einer Methode, dann wird das
lokal angelegt.
Notation.
Wenn Sie es global anlegen, dann ist es eine globale Variable.
Wie Sie vorhin anworten, sie ist nicht auf der Halde.
Wenn Sie ein Objekt dynamisch anlegen wollen, dann brauche ich einen
Zeiger, den ich C4 nenne, der auf ein Objekt vom Typ Groß-C zeigt.
Und das bedeutet, der Zeiger ist der Stern.
Dann haben Sie aber nur den Zeiger gekriegt und nicht das Objekt.
Sie brauchen jetzt noch das Objekt selbst, das muss noch angelegt
werden, also das, was im New passiert.
Und das rufen Sie auf, indem Sie Malloc aufrufen.
Malloc heißt Memory Allocation, also Speicherbelegung.
Der Malloc hat natürlich auch keine Ahnung in C von Typen und Klassen
und sowas, sondern Sie müssen sagen, wie viel Byte dieses Ding
braucht.
Sie sagen einfach nur, ich bräuchte jetzt ein Stück Speicher mit dem
Malloc und das soll so groß sein, dass das C reinpasst.
Also sagen Sie ihm Size of C.
Das liefert Größe zurück in Bytes.
Dann kriegen Sie jetzt ein Zeiger auf ein Objekt, das hat genauso viel
Platz wie C an Attributen erfordert und das Malloc gibt Ihnen ein Void
zurück.
Das hat auch keinen Typ, also müssen Sie auch noch eine Typumwandlung
machen und das bitte schön jetzt als Zeiger auf C interpretieren.
Das sagen Sie so, C Stern Typ.
Und schließlich das weisen Sie auf das C4 zu.
Das ist also schon etwas aufwendiger zu schreiben, aber im Prinzip das
gleiche wie bei New, nur dass jetzt kein Konstruktor abläuft, sondern
Sie kriegen ein uninitialisiertes Stück Speicher, in dem Sie Ihre
Attribute ablegen können.
Wie greife ich darauf zu?
Ach so, noch etwas zu sagen, in C läuft kein Speicherbereiniger
automatisch ab.
Wenn Sie solche Dinge dynamisch anlegen, dann müssen Sie sie auch
wieder freigeben.
Es sei denn, Sie legen nur an und müssen nie freigeben, weil Sie die
Objekte die ganze Zeit benutzen.
In C auch, ja.
Brauchen Sie nicht?
Kommt wahrscheinlich auf den Compiler an.
Dummerweise ist jetzt aber die Zugriffsfunktion eine andere oder die
Zugriffsnotation eine andere.
Vorhin hatten wir gesagt, um da Attribute anzusprechen, sagen Sie nur
C1.A1.
Jetzt müssen Sie sagen C4', also Pfeil A1.
Das heißt, der Unterschied zwischen Referenz und statisch angelegtem
Objekt ist immer sichtbar in C.
Den Unterschied kriegen Sie nicht weg.
Aber, naja, so ist C.
Wenn Sie also lokal in einer Methode zum Beispiel sagen, sagen wollten
Sie so etwas wie New C, dann sagen Sie, gut, lege ich das einfach an
auf dem Keller per Groß C, wie in dieser Form, dann verschwindet
dieses Objekt wieder, sobald die Methode, in der es angelegt wurde,
aufhört, Block verlassen wird, genauer gesagt.
Dann brauchen Sie auch keine Speicherbereinigung.
Die ist automatisch, oder dafür fliegt Ihnen das Objekt weg, wenn die
Methode verlassen ist.
Dann ist es einfach verschwunden.
Oder aber, Sie legen es auf diese Art und Weise an, mit Merlock, dann
sind Sie aber auch verantwortlich für die Speicherbereinigung.
Es gibt jede Menge C-Programme, die keine Speicherbereinigung
durchführen, wo der Programmierer es auch nicht macht, weil es nicht
notwendig ist, weil nicht so viele Objekte entstehen, sodass der
Speicher trotzdem ausreicht.
Jedenfalls müssen Sie sich etwas überlegen dazu.
Das ist eine der Hauptfehlerquellen von C, der Speicherbereinigung.
So, der letzte Punkt in dieser Reihe ist, und was machen wir jetzt mit
Methoden?
Methoden gibt es nicht.
Also, die Koppelung von Methoden an Klassen gibt es einfach nicht.
Es gibt nur sogenannte freistehende Funktionen.
Sie können einfach irgendwelche Funktionen definieren, in C, und die
sind nicht an ein Objekt oder eine Klasse gebunden.
Sie können natürlich Parameter haben.
Das führt dazu, dass wir es folgendermaßen machen, wenn Sie eine
Methode haben, die bestimmte Parameter benötigen, und an der Methode
selbst drin auf ein Objekt zugreifen, also dieses M ist natürlich an
eine Klasse gebunden, innerhalb dieser Klasse gibt es Attribute, und
das sind die Attribute hier, die Sie brauchen, schreibend oder lesend.
Bei freistehenden Funktionen sieht es einfach so aus in C, da sagen
Sie, gut, dann mache ich M, mit den Parametern, die ich gehabt habe,
aber ich habe jetzt als ersten Parameter standardmäßig das Objekt, an
dem ich die Methode ausrufen will, und das wäre in diesem Falle ein
Zeiger auf C, und ich nenne das jetzt einfach Objekt.
Man kann natürlich auch O schreiben.
Also einfach standardmäßig das Objekt, auf dem sich der Methodenaufruf
bezieht, als ersten Parameter der freistehenden Funktion.
Und so übrigens implementiert das auch C++.
C++ wandelt Methoden genau in das um.
Und wenn Sie jetzt auf Attribute zugreifen wollen, geht das nicht mehr
einfach durch Attribut hinschreiben, wie das ja in Java so schön ist,
sondern Sie müssen natürlich jetzt diesen Parameter benutzen.
Von diesem Objekt, Punktattribut oder Objekt, also Pfeilattribut,
eigentlich meistens das hier.
Das ist also der Teil.
Einen Aufruf selbst hatten wir noch nicht, also der Aufruf C4.M und
Parameter wird also umgewandelt in Aufruf C4 mit C4 als ersten
Parameter.
Klar dazu?
Jetzt können Sie C programmieren.
Okay.
Ach so, Vererbung.
Vererbung hatten wir noch nicht.
Gibt es natürlich in C auch nicht.
Da kann man sich aber auch wieder behelfen.
Wie heißt das behelfen?
Sie nehmen die Oberklasse, schauen nach, was da für Attribute drin
sind, kopieren die und setzen sie in der Unterklasse ein.
Die Information, dass das eine Unterklasse war, ist futsch.
Informationspolymorphie gibt es auch nicht.
Wenn Sie Polymorphie auch noch haben wollten, dann kann man das auch
erreichen in C mit etwas Arbeit.
Man muss nämlich dann zu diesen Strukturen, ich gehe mal zurück, zu
einer Struktur hier oben noch sogenannte Funktionszeiger hinzufügen.
Das sind ja Daten, aber solche, die auf die Methoden zeigen.
Das wird dann in der Programmierung in C, ist es im Prinzip möglich
und ausnahmsweise kann man das schon mal machen, aber wenn Sie das
häufig machen müssen, dann gehen Sie halt gleich zu C++.
Sie können ja auch C und C++ mischen.
Also es geht, aber das müssen wir jetzt nicht durchprobieren, wie das
im Detail funktioniert.
Aber als man noch eben ohne Objektorientierung zum Beispiel grafische
Benutzeroberflächen macht, zum Beispiel das X-Fenster-System,
funktioniert noch auf diese Art und Weise.
Wir hatten Klassen, die eigentlich nur Strukturen waren und diesen
Strukturen standen bei Zeiger auf Methoden, die eine bestimmte
Parameterliste erwarteten und es gab auch Schnittstellen, das waren
einfach Klassen ohne Attribute, Strukturen ohne Attribute, die auch
nur wieder Zeiger auf Methoden enthielten.
Der Aufruf ist dann auch wieder etwas komplizierter und da muss man
natürlich das Objekt hernehmen, das Attribut auflösen und das Casten
auf einen Funktionszeiger und dann damit aufrufen.
Das ist also möglich, aber wie gesagt, mühsam.
Das sprechen wir hier nicht.
Und jetzt kommen die Assoziationen und wie implementiere ich
eigentlich Assoziationen?
Die kommen ja nur in OML vor und in keiner objektorientierten Sprache
gibt es Assoziationen.
Das ist eigentlich schade.
Eigentlich sollte jemand eine neue Sprache entwickeln, die mit
Assoziationen arbeitet, anstelle mit Referenzen wie in Java.
Wir müssen also jetzt sehen, dass es unidirektional oder bidirektional
geht.
Wir müssen die Kardinalitäten und die Kardinalitäten berücksichtigen.
Viele Zeiger oder ein Zeiger.
Also wir hatten zum Beispiel die Information, ich soll von Person auf
Konto kommen.
Jede Person hat genau ein Konto.
Das nenne ich Gehaltskonto und die Navigation zurück ist nicht
erforderlich.
Das ist ungefähr die einfachste Implementierung, die Sie sich wünschen
können und es sieht natürlich so aus, dass Sie eine klasse Person
anlegen dafür.
Die hat eine private Variable, die nenne ich Gehaltskonto.
Die ist dann vom Typ Konto.
Das ist natürlich auch eine Klasse.
Das geht also.
Und wenn ich eine Person anlege, muss ich schon dafür sorgen, dass
bereits exakt ein Konto zugewiesen ist.
Also muss ich im Konstruktor, dem Gehaltskonto, ein neues Konto
zuweisen.
Damit habe ich natürlich diese Eins erreicht.
Vom Konto kann ich nicht mehr zurück.
Also da muss ich kein Rückwärtsattribut einsetzen.
Und um das Gehaltskonto abzugeben, habe ich eine Funktion, die mir das
liefert und sagt dann Return Gehaltskonto.
Das ist die einfachste Art und Weise, eine nicht rückwärtsverfolgbare,
einstellige Kardinalitätsassoziation zu verwirklichen.
Das ist ziemlich einfach.
Wie machen wir das hier?
Ich will in beiden Richtungen marschieren können.
Naja, klar, das mache ich so, dass ich hier einen Verweis auf Person
und hier bei der Person einen Verweis auf Konto habe.
Also gut, wie sieht das aus?
Wir haben natürlich jetzt gegenseitige Abhängigkeiten.
Das heißt, wenn ich eine Person habe, brauche ich auch das
Gehaltskonto gleich.
Und wenn ich das Gehaltskonto habe, muss ich sofort eine Person dazu
haben.
Sonst habe ich keine Eins-zu-eins-Relation von Beginn an.
Und hier steht ja nicht Stern und Eins, sondern es steht genau Eins.
Das heißt immer genau Eins.
Dafür muss ich jetzt programmatisch sorgen.
Wie mache ich das?
Ich brauche natürlich bei der Person ein Gehaltskonto.
Wenn ich eine neue Person anlege, dann hole ich mir ein neues Konto
und gebe mich selbst mit für den Rückwärtsverweis.
Und gebe Gehaltskonto, ist klar.
Umgekehrt, das Klasse-Konto sagt, ich brauche einen Besitzer.
Wenn ein Aufruf kommt, der sagt, wer ist mein Besitzer, dann merke ich
mir das.
Und schon habe ich das erreicht.
Das heißt, wenn ich ein Konto erzeugen will, muss ich auch schon eine
Person mitliefern.
Und die muss besser schon auf mich zeigen, bevor es losgeht.
Also erst würde ich jetzt eine Person anlegen, die erzeugt ein neues
Konto mit das drin.
Schon klappt das.
Die Initialisierung von Konto aus ist nicht so schön, weil ich hier
erst noch einen Besitzer erzeugen muss.
Aber ich kann auch das hier aufrufen, diesen Konstruktor mit New
Person.
Da habe ich auch wieder eine zugehörige Situation.
Allerdings klappt das doch nicht so recht.
Wenn ich also hier New Konto mit einer Person aufsetzen würde, dann
lüge ich mir in die Tasche.
Dann wird das hier aufgerufen.
Da wird schon ein neues Konto angelegt.
Das heißt also, jetzt habe ich plötzlich ein Konto, ein neues Konto,
das auf Person zeigt, aber Person zeigt auf dieses Konto.
Das ist also nicht symmetrisch.
Ich muss also in diesem Falle wissen, mit Person anfangen.
Okay, wir machen nächstes Mal weiter.
Vielen Dank.