Letztes Mal hatten wir mit dem Kompostum aufgehört und da habe ich
Ihnen ja gesagt, diese Struktur, die findet man immer wieder, nämlich
eine Oberklasse, mindestens zwei Unterklassen, wenn nicht noch mehr,
wovon eines ein Aggregat ist, das zurückweist auf die Oberklasse.
Und damit kann ich in das Aggregat sowohl ein Blatt, außer auch ein
weiteres Kompostum einhängen.
Und eigentlich gibt es dafür keine andere Lösung, wenn Sie also einen
rekursiven Aufbau haben wollen, wo sowohl in einem Kompostum, sowohl
wieder ein weiteres Kompostum, also ein zusammengesetztes Objekt oder
ein Blatt vorhanden sein sollen.
Da brauchen Sie diese allgemeine Oberklasse.
Und ich hatte dann damit aufgehört, dass ich sagte, gibt es jetzt eine
Geschmacksrichtung.
Die Geschmacksrichtung heißt, mache ich das so, dass die Methoden, die
zum Aufbau und Abfragen des Kompostums gebraucht sind, nur im
Kompostum, das ist eine Möglichkeit, eine andere Möglichkeit war, dass
Sie hier ebenfalls auch selbst für Blätter vorgefertigte Kompositor
-Operationen haben, die nichts tun oder nur Fehlermeldungen geben.
Wie gesagt, geschmacklich, je nachdem, was Sie wollen, wenn es
vorkommt in einem Programm, dass Sie ein Objekt haben, das eigentlich
ein Blatt ist, aber Sie wollen es gar nicht wissen, Sie wollen halt
irgendwelche Standard-Operationen-Elemente hinzufügen und dann sagen
Sie, wenn es ein Blatt ist, dann will ich die Unterscheidung nicht
haben, ich will trotzdem hinzufügen, aufrufen können, dann machen Sie
es auf die Art und Weise.
Kommt eigentlich mir selten vor, also meistens findet man, finde ich,
gerade in Java und C++ findet man diese Struktur.
Das ist kein großartiger Unterschied, wirklich.
So, das war also das.
Jetzt kommen ein paar Beispiele.
Zusammengesetzte Grafiken zum Beispiel sehen so aus.
Sogar die Java, Swing und AVT-Bibliotheken enthalten Kompositor.
Zum Beispiel gibt es in AVT, das ist der uralte Grafikbibliothek für
Java, heißt Abstract Windows Toolkit, ist also schon von Anfang an auf
verschiedenen Plattformen ausführbar gewesen.
Da gibt es eine Komponente und von dieser Komponente gibt es einen
Unterklassekontainer und hier drüben ein Checkbox.
Hier ist das Blatt, hier ist der Behälter und dieser Behälter selbst,
oder das Kompositor, hat viele, viele Unterklassen.
Und wie viele zeige ich Ihnen jetzt gleich, aber Sie sehen sofort
wieder diese Struktur, Unterklasse, Oberklasse, Schnittstelle
wahrscheinlich oder auch teilweise abstrakt, Aggregationsbeziehung und
Blatt oder weitere Kompositor.
Und ein Ausschnitt aus dieser Bibliothek, mit der man grafische
Benutzoberflächen macht, mit der wir uns später dann im
Implementierungskapitel auch beschäftigen werden, sagt, so ist das J
-Komponent.
Naja, das Einfachste davon ist ein J-Label, also eine Beschriftung auf
einem Objekt, zum Beispiel einem Knopf oder auf einem Eingabefeld.
Combo-Box ist eine Zusammenführung von einer Liste mit einem Schalter,
ein Abstrakt-Button, den können Sie weiter unterscheiden.
Da gibt es einen J-Button, der kommt sehr häufig vor, das ist die
normale Schaltfläche.
Und dann gibt es das J-Menu-Item, das ist also das Element, das Sie in
ein Menü einbauen und ein J-Toggle-Button, das ist einer, den Sie ein-
und ausschalten können.
Und da gibt es unterschiedliche, auch davon gibt es wieder
unterschiedliche Klassen.
Also die Klassen-Hierarchie muss nicht auf diese zwei Ebenen
beschränkt sein.
Und es ist auch keine Erfindung von mir, sondern es ist wohlbekannt,
dass man so komplexe Dinge aufbauen kann.
Ein weiteres Beispiel hier nehmen wir an.
Sie wollen so ein Eingabefeld eingeben, aufbauen.
In Java, wie macht man das?
Das macht man so, indem man ein Kompositor bildet.
Also wir haben hier diese Textfelder für Vorname, Nachname, Straße,
Hausnummer, Postleitzahl, Ort, Bezahlweise.
Da gibt es ein Menü, ein Pulldown-Menü hier und zwei Knöpfe.
Nö, doch nicht.
Kaufen, jawohl.
Das äußerste oder oberste Element in diesem Kompositum, diesem
zusammengesetzten Objekt, ist ein Fenster.
Also als erstes müssen Sie einen sogenannten J-Frame anlegen.
Das ist das Hinterste hier.
Da können Sie Schließknopf zum Beispiel dran haben, vergrößern und so
weiter.
Das kommt automatisch mit.
Und Sie müssen nur eine Beschriftung mitgeben.
Dann dieses J-Window hat ein Paneel.
In dieses Paneel können Sie jetzt Dinge einsetzen.
Das ist zunächst mal einfach nur weiß, enthält gar nichts.
Und dem können Sie jetzt einsetzen.
Moment, ich muss ein bisschen weiter klicken.
Ein weiteres J-Panel.
Und in dieses J-Panel setzen Sie einen J-Textfield und ein J-Label und
ein J-Panel und so weiter, die anderen Dinge.
Das heißt also, Sie schreiben tatsächlich, wenn Sie dieses Ding haben
wollen, schreiben Sie tatsächlich ein Programm, das es Stück für Stück
aufbaut.
Mit Add, Add Component.
Also das sind alles Komponenten.
Die fügen Sie einfach zu diesem letzten Panel hinzu.
Hier unten ist noch ein weiteres, separates.
Das macht man in diesem Falle, damit diese Knöpfe eben nicht mit dem
Zeug hier vermischt sind.
Wie man das so arrangiert, dass es gerade ausgerichtet ist und so
weiter in diesem Netzwerk, das sehen wir später mal.
Da können Sie sich dann die entsprechenden Anordner anschauen.
Da gibt es sehr viele Anordner.
Gut, das ist auch ein Beispiel dafür.
So, also wann benutze ich das Compositum?
Naja, das können wir jetzt verstehen.
Wenn wir Behälterdatenstrukturen haben wollen, die Komponenten
speichern, wenn Bestandshierarchien entstehen sollen, zum Beispiel
grafische Benutzoberflächen.
Wenn es möglich sein soll, dass diese Hierarchie bearbeitet wird, ohne
dass der einzelne Programmierer jeweils wissen muss, was genau er in
der Hand hat.
Das heißt, ich kann ein rekursives Programm beschreiben, das ein
Compositum abwandert, indem ich bemerken kann, ob ich ein Blatt in der
Hand habe oder ein Compositum.
Beim Compositum dann die Methoden aufrufe, mit denen man die einzelnen
Elemente herausholt.
Zum Beispiel das Element mit einer bestimmten Nummer abzuholen oder
mit einem Iterator.
Was man hier noch hinzufügen sollte ist, ich sagte gerade von einem
rekursiven Ablauf.
Wenn ich so etwas habe, also eine tiefe Struktur, dann kann ich
natürlich das auch kombinieren mit dem Besucher.
Wenn Sie sich erinnern, wir hatten letztes Mal gesprochen über den
abstrakten Syntaxbaum für eine Zuweisung, linke und rechte Seite, wo
die rechte Seite im Ausdruck war und so weiter.
Und wir hatten dazu einen Besucher gebaut, der einfach eine Klasse ist
mit einzelnen Operationen, die auf die einzelnen Knotentypen
zugeschnitten waren.
Wir sagen, wir werfen den Beobachter in die Nimm-Entgegen-Methode ein,
die wir am Wurzelobjekt aufrufen und dann läuft ein rekursiver Ablauf.
Der Besucher wandert dann durch den ganzen Baum hindurch.
Das war natürlich ein Kompositum, dieser abstrakte Syntaxbaum war
nichts anderes als ein Kompositum.
Und die Blattknoten waren die einzelnen konstanten oder variablen
Referenzen und die Kompositumsknoten waren zum Beispiel der Additions-
oder der Zuweisungsknoten.
Man sieht das jetzt also oft, dass man ein komplexes Kompositum hat,
das man mithilfe von Besuchern bearbeitet.
Okay, gerade im Compilerbau ist das sehr beliebt.
So, jetzt kommen noch ein paar Details über die
Implementierungsmöglichkeiten.
So ein wichtiges Muster kann man sich vorstellen, dass man sich
unterschiedliche Implementierungsmöglichkeiten ausgedacht hat.
Eines davon ist, dass man vielleicht einen Rückwärtsverweis braucht,
wenn ich also bei einem Knopf irgendwie von irgendwoher bekomme und
gerne wissen möchte, woher in welchem Paneel dieser Knopf sitzt, zu
welchem Fenster er gehört.
Dann muss ich ja eigentlich die Bestands-Hierarchie nach oben
durchwandern können.
Wie mache ich das?
Ich mache das, dass ich die Assoziation, die Aggregats-Assoziation,
die wir hatten, zweiseitig ausbilde.
Das heißt also, wenn ich einen Rückwärtsverweis habe, sodass ich von
jedem Blatt zum nächsten Kompositum, von dem Kompositum zum nächsten,
übernächsten und so weiter, nach oben wandern kann.
Und natürlich muss ich dafür sorgen, dass diese Referenzen stimmen.
Wenn immer so ein Knoten verlegt wird, müssen sowohl Vorwärts- als
auch Rückwärtsverweis korrigiert werden.
Das sollte klar sein.
Allerdings gibt es da ein Problem, wenn ich den Fall habe, dass dieses
Blatt, dieser Blattknoten zu zwei Kompositor gehört.
Wohin zeigt jetzt die Rückwärtskante, die Rückwärtszeiger?
Das ist natürlich unklar.
Daher ist, wenn man Knoten teilt, vorsicht geboten mit dem
Rückwärtsverweis, man muss sich dann festlegen, meistens auf einen,
sagen wir zum Beispiel den, der wichtiger ist oder der am Ursprung
angelegt wurde.
In Linux-Systemen und Unix-Systemen gibt es im Dateisystem auch dieses
Problem.
Denken Sie sich jetzt also zum Beispiel, dieses Blattknoten wäre eine
Datei.
Normalerweise sitzt eine Datei in genau einem Verzeichnis.
Und das macht man deswegen, weil man wissen möchte, wenn man zum
Beispiel ein Verzeichnis löscht, ob alle Nachfolger dieses
Verzeichnisses auch gelöscht sind.
Dann will man natürlich das Verzeichnis selbst löschen, sonst würde
man einen Fehler ausgeben.
Verzeichnis nicht leer, bist du sicher, dass du das wirklich alles
löschen willst und sowas.
Also müsste man, und das geht jetzt nicht mehr, wenn man die Datei an
mehreren Stellen nicht kopiert, sondern nur eine Datei hat, dann habe
ich plötzlich zwei Vorgänger, zwei Elternknoten, zwei Composeter, wo
es hingehört.
Wer weiß, wie das gelöst ist in Linux?
Einer?
Noch jemand?
Ja, bitte.
Meine Frage war jetzt anders.
Wenn Sie eine einzige Datei in mehreren Composeter haben wollen, wie
ist das gelöst?
Da gibt es einen Fachbegriff.
Wissen Sie den?
Unterschied zwischen Hardlink und Softlink oder Hardlink und Symbolic
Link.
Die machen also tatsächlich das Richt, so wie ich es erwähnt hatte.
Da gibt es eine feste Vorwärtsverzeihung mit Rückwärtsverzeihung zu
dem Kompositum.
Das ist meistens das erste Mal, wo die Datei erzeugt wird, wird
festgelegt.
Wenn Sie jetzt an einer anderen Stelle sagen, die Datei hätte ich
gerne auch an der Stelle, aber ich will keine Kopie haben, sondern die
gleiche Datei, dann müssen Sie hier ein sogenanntes Symbolic Link
anlegen und das sieht so aus, dass Sie sagen, irgendwo haben wir einen
gemeinsamen Vorgänger in dieser Dateihierarchie und ich schreibe hier
rein einen Pfad, der bis zu diesem Blatt führt und den nenne ich
natürlich genauso, Entschuldigung, dieses gemeinsame Blatt.
Das ist ein Verweis anderer Art, das ist eigentlich nur eine
Zeichenreihe, die mir den Pfad zum eigentlichen Objekt liefert.
So, und dann kann es natürlich passieren, dass Sie diese Datei
löschen, aber dieses Symbolic Link oder Softlink oder sowas noch
weiter besteht, aber der stört das Dateisystem nicht, das Dateisystem
ist weiterhin noch intakt.
Das Problem ist immer, wissen Sie, wenn Sie jetzt zwei Verweise
hätten, zwei harte Verweise auf diese Datei, diese Datei löschen und
diese aber nicht in beiden Compositor löschen, dann haben Sie einfach
einen Verweis ins Nirvana auf ein gelöschtes Objekt.
Und das kann irgendwann mal wieder zugewiesen werden und dann etwas
ganz anderes sein.
Das will man vermeiden.
Da hat man sich also so beholfen.
Okay, gibt es dazu Fragen?
Also schauen Sie mal nach in Ihrem Linux-System, was ein Symbolic Link
ist.
Wenn Sie eins haben.
Opa, wir sind zu weit gegangen.
So, das haben wir eigentlich schon durchbesprochen.
Was wir möchten ist, dass in der allerobersten Klasse des Compositums
eine möglichst breite Schnittstelle existiert, die möglichst alle
Funktionen hat, die man jemals von einem Blatt oder einem Compositor
erwarten würde.
Und dann die Frage mit den Kinderobjekten hinzufügen und fernen für
Blattobjekte haben wir schon behandelt.
Über das rede ich jetzt nicht noch mal.
Wie werden eigentlich die Kinderelemente gespeichert?
Nun, da haben Sie viele Möglichkeiten.
Sie können natürlich Felder hernehmen, Listen, Vektoren, Mengen,
Collections, Hash-Tabellen, wie Sie es gerade brauchen.
Da sind Sie völlig frei, welche Datenstruktur Sie benutzen.
Wenn Sie eine feste Anzahl von Nachfolgerknoten haben, z.B.
zwei, wie z.B.
in einem Binärbaum, dann nehmen Sie keines dieser Methoden, sondern
benutzen explizite Links und geben den Namen, z.B.
links und rechts.
Und die Zugriffsmethoden heißen dann hole links, hole rechts, füge
hinzu oder add left, add right.
Dass Sie also explizite Namen für die Nachkommen haben, anstelle einer
anonymen Liste.
Aus dem Auslesen der Kinderprojekte kann man natürlich mit dem
Iterator oder einem Enumerator benutzen.
Das geht insbesondere dann besonders leicht, wenn Sie Felder, Listen
oder Mengen haben.
Die bieten diesen Iterator schon an.
Den brauchen Sie also nur noch auszugeben.
Dann, wenn wir Felder, Listen oder Mengen haben, dann ist es manchmal
auch wichtig, die Reihenfolge der Nachkömmlinge oder Kinder,
Kindobjekte festzulegen.
Das war zum Beispiel hier, gehen wir zurück auf dieses Ding hier, ist
es wichtig, da wollen Sie ja diese Nachkommensobjekte, Vorname,
Nachname, Straße, Hausnummer usw.
in genau dieser Reihenfolge haben, nicht in einer zufälligen.
Und dann können Sie natürlich eine Herstabelle oder eine
Mengenstruktur nicht nehmen, wenn sie mir keine Ordnung liefert.
Aber Listen und Vektoren würden das liefern.
Mit Listen und Vektoren kann ich selbstverständlich die Reihenfolge
festlegen.
Und wie wir gleich sehen werden, wenn wir über Layoutmanager kurz
sprechen, diese Anordner von grafischen Objekten auf
Behälterstrukturen, da brauche ich diese Reihenfolge.
Also auch das geht.
Okay, dann gehen wir noch mal zur Strategie, aber vorher noch zur
Compositum.
Gibt es dazu noch Fragen?
Also sehr häufig benutzte Struktur zum Aufbewahren, zum Speichern und
Durchwandern von Bestandshierarchien.
Auch Bäume, wie gesagt, wenn Sie geteilte Knoten haben, dann bekommen
Sie nicht nur einen Baum, sondern Sie bekommen einen gerichteten
Graphen.
Und da sollten Sie selbstverständlich darauf achten, dass er a
-zyklisch bleibt, denn sonst versagen alle rekursiven Algorithmen an
einem zyklischen Graphen.
So, die Strategie.
Die Strategie haben Sie ganz am Anfang schon mal gehört.
Wir schauen sie uns noch mal ganz kurz an.
Die Idee ist, dass Sie eine Familie von Algorithmen anbieten wollen
und das Strategiemuster macht das möglich.
Zum Beispiel sieht das eben so aus, dass Sie sagen, ich habe eine
allgemeine Oberklasse.
Das ist entweder eine Klasse oder meistens eigentlich nur eine
Schnittstelle.
Die gibt mir eine Algorithmenschnittstelle vor.
Das ist natürlich nicht nur eine Methode, sondern kann beliebig viele,
so viele man halt braucht.
Und dann haben Sie eine konkrete Strategie A bis C mit der
Algorithmschnittstelle darunter.
Und ein Beispiel dafür wäre ein Übungsblatt von 2014 im Programmieren
Vorlesung.
Ich weiß nicht, ob Sie die wieder hatten.
Es ist irgendwie Wintersemester.
Warten Sie, ich gebe Ihnen das erste.
Warteschlangen-Simulation.
Sie modellieren ein Warteschlangen-System.
Haben Sie das vielleicht auch gemacht?
Okay.
Na gut, dann muss ich es vielleicht ein bisschen ausführlicher
erklären.
Da sollte also ein Warteschlangen-System gebildet werden.
Das ist das eine.
Sie müssen den Text nicht vollständig lesen.
Sie bekommen also Aufträge herein.
Die haben eine bestimmte Dauer.
Die stellen Sie in Ihre Warteschlange und haben eine Bedieneinheit.
Diese Bedieneinheit soll ein Element nach dem anderen aus dieser
Warteschlange abholen.
Danach sehen, wie lange es dauert und dann die Systemzeit, das ist
eine Simulationssoftware, um diese Länge weiterschalten.
Mehr ist da nicht zu tun, sodass man sehen kann, wenn ich eine
bestimmte Anzahl von Aufträgen in das Warteschlangen-System eingebe,
wie lange dauert die Bearbeitung.
das Trickreiche dabei ist, dass man unterschiedliche Warteschlangen
hernehmen sollte in dieser Aufgabe, nämlich einfach eine
Warteschlange, einen Keller und eine Prioritätsschlange.
Das heißt, bei da geben Sie auch noch Priorität ein und sagen, der
kommt zuerst.
Oder der hat Priorität 1, 5 oder so etwas.
So, also jetzt ist die Aufgabe schon ein bisschen schwierig, nämlich
Sie sagen, also gut, ich brauche einen Simulator.
Der Simulator sollte also eine Möglichkeit haben, unterschiedliche
Warteschlangen abzuarbeiten.
Also muss es mir möglich sein, diese Warteschlangen auszutauschen.
Und das riecht einfach sofort nach Strategie.
Und genauso machen wir das auch.
Wir benutzen auch hier eine Strategie, nämlich Sie nehmen das
Strategiemuster von hohen und tauschen die Bezeichner aus.
Dann sind Sie soweit fertig, zumindest was das UML-Diagramm betrifft.
Das Codieren fällt natürlich auch noch fest.
Also hier ist die Strategie-Algorithmus-Schnittstelle, konkrete
Strategie ABC.
Die Nummer stimmt auch schon, die Anzahl.
Und jetzt tauschen wir die Dinger aus und sind fertig.
Das heißt, der zweite Bereich, da gibt es etwas hinzuzufügen und etwas
zu entfernen, also den nächsten drankommenden Auftrag entfernen.
Und Warteschlange, Keller, Purity-Schlange.
Diese Dinger hier müssen Sie jetzt natürlich jeweils implementieren.
Und natürlich implementieren Sie das Zeug nicht vom Grund auf, sondern
Sie adaptieren etwas.
Das passende wäre java.util.q und das andere, das Sie adaptieren ist
java.util.stack.
Da müssen Sie natürlich jetzt eine Funktion hinzufügen, die heißt,
einen Auftrag hinzufügen.
Da muss natürlich die Schnittstelle stimmen.
Das heißt, dieses Add wird umgewandelt in einen Push- Funktion,
Methodenaufruf.
Das adaptieren, das kennen wir auch.
Und Priorität-Q gibt es auch oder vielleicht muss man die selber
implementieren.
Das weiß ich jetzt nicht mehr.
So, jetzt kann ich Folgendes machen.
Ich kann mir in meinem Simulator eine Variable vorgeben, die heißt W
für Wartebereich und ist vom Typ Wartebereich.
Wenn man die Aufgabe noch durchliest, kann man in der Eingabe sagen,
hier sind die Aufträge.
Da gibt man nur deren Dauer an.
Eventuell deren Priorität.
Und welche Warteschlangen-Disziplin man möchte.
Dann habe ich also in meinem Hauptprogramm einfach nur eine einzige
Variable, die heißt W vom Typ Wertebereich.
Auf die weise ich eine Instanz der gewünschten Warteschlangen-Typs zu.
Also sage ich zum Beispiel W ergibt sich zu new Q und von dann läuft
genau das gleiche Programm für alle Warteschlangen da, nämlich
Aufträge auffüllen, reinfüllen und dann den Simulator laufen lassen,
die Aufträge eins nach dem anderen abzuholen, die Wartezeiten
hochzuschalten und zu sehen, wie lange es am Schluss gedauert hat.
Das wäre so ein Beispiel dafür.
Es gibt noch mehr.
Das können Sie sich selber anschauen.
Netzwerkverbindungen zum Beispiel könnten so aussehen, dass Sie eine
Netzwerkschnittstelle haben, das ist typischerweise öffnen, schließen,
senden und empfangen.
Und du kannst eine Implementierung für Ethernet, Wireless-Line und
UMTS geben, unterschiedliche Netzwerkschnittstellen.
Der Punkt dabei ist immer, Sie wollen das Klientenprogramm, also die
Netzwerkverbindung schreiben, sodass es unabhängig ist davon, was Sie
hier unten wählen.
Ähnlich wie beim Modul, da hatten wir auch gesagt, man wähle eine
änderungsunempfindliche Schnittstelle.
Das ist die hier.
Schreibe mein Programm auf diese Schnittstelle hin, was wir jetzt mit
der Strategie zusätzlich können.
Wir sind nur nicht geschützt davon, dass sich diese Schnittstelle
hoffentlich nicht ändert.
Wir können auch noch die Implementierungen austauschen.
Und zwar zur Laufzeit.
Okay.
Gibt es das wirklich?
Kommt das wirklich vor?
Natürlich kommt das vor.
Und als Beispiel gebe ich Ihnen jetzt die Layout Manager oder Anordner
in Java.
Das ganze Code ist ein bisschen klein zu lesen, aber wichtig ist
erstmal diese Funktion hier oben.
Entschuldigung, Layout Container, die hier.
Layout Container.
Da ist eine Add-Funktion, das ist natürlich auch wieder ein Kompostum
im Hintergrund dabei.
Da kann ich etwas hinzufügen, aber das Wichtige ist diese Layout
-Funktion.
Die Layout-Funktion in Java Swing, deren Zweck ist, wenn Sie mir einen
Zeichenbereich geben in einem Fenster und sagen, ich habe drei Knöpfe
und ein Menü und ein Texteingabefeld oder zwei oder drei, dann muss
ich die in irgendeiner Form anordnen.
Geometrisch ausrichten.
Und dazu gibt es verschiedene Layout Manager, die das können.
Und die unterscheiden sich sehr in ihrer Funktionalität und auch in
der Schwierigkeit, sie zu bedienen.
Zum Beispiel gibt es einen ganz einfachen Flow-Layout-Manager.
Der tut nichts, außer die Elemente, die zu dieser Komponente gehören,
der Reihe nach anordnen.
Und wenn das Fenster zu klein ist, dann beginnt er einfach mit der
nächsten Zeile.
Wenn Sie also 25 Knöpfe haben, dann werden sie der Reihe nach
angeordnet und dann gibt es halt mehrere Reihen von Knöpfen.
Die werden dann so breit, wie die Beschriftung auf den Knöpfen ist.
Der ist ziemlich einfach.
Aber was macht der?
Naja, der implementiert den Layout-Container-Funktion auf seine Art
und Weise.
Und er braucht natürlich jede Menge anderer Informationen darüber,
über die Dimensionen, die Größe des Fensters und die Komponenten usw.
Diese Hilfsfunktionen gibt es natürlich auch.
Er kann auch sagen, was seine bevorzugte Größe ist.
Wenn Sie also das Fenster aufklappen das erste Mal, dann kommt es
wahrscheinlich immer in der gleichen Größe.
Das ist in der Regel die bevorzugte oder preferred size.
Dann gibt es den Border-Layout.
Den Border-Layout werden Sie auch noch sehen.
Den Border-Layout, das muss ich jetzt mit den Händen anzeigen, weil
ich kein Bildchen habe.
Border-Layout hat nur fünf Komponenten.
Nämlich einen Balken oben, ein Feld in der Mitte, einen Balken unten
und zwei linke und rechte Balken.
Also fünf Stück oben rüber, links und rechts, unten und in der Mitte
ein etwas größeres Feld.
Und wenn Sie einfach sagen, Sie können ich weiß nicht was, Ost, West,
Süd, Norden oder so etwas diesen Feldern ihre Elemente zuweisen, dann
werden die innerhalb dieser fünf Teile ausgerichtet.
Das ist oftmals sehr praktisch, wenn man dann zum Beispiel unten
einfach nur noch eine Reihe von Knöpfen braucht.
Dann macht man oben ein Border-Layout, da ist irgendwie eine Grafik
drin, für die Mitte, oben links und rechts lässt man leer und unten
tut man die Knöpfe rein und dann hat man ein sauberes Layout, ohne
viel zu programmieren zu müssen.
Das wäre ein Border-Layout, funktioniert sehr schön und das Grid
-Layout ist eines der kompliziertesten, da geben Sie sich ein Gitter
vor und Sie orientieren die einzelnen Elemente an diesen
Gitterklammern.
Das ist also wie gesagt händisch recht schwierig zu programmieren,
deswegen gibt es natürlich auch grafische Benutzeroberflächen, die mir
erlauben, das zu zeichnen und dann in ein Programm umzuwandeln.
Okay, soweit zum Strategiemuster.
Achso, vergessen habe ich jetzt natürlich, wo gehört das denn hin?
Dieser Layout-Manager ist Teil dieses Containers, das war unser
Kompositum, da gibt es eine Funktion, eine Methode, die heißt Set
-Layout -Manager und da geben Sie einfach an, Set-Layout und geben dem
einen Layout-Manager, zum Beispiel den Border-Layout.
Damit setzen Sie fest, wie das Ding dargestellt werden soll.
Können Sie im Laufe des Programmes, wenn nötig, sogar ändern.
Okay, so.
Strategie.
Anwendbarkeit, wenn viele verwandte Klassen nur in dem Verhalten
unterscheiden, viele, das sind schon mal drei Strategieobjekte, bieten
die Möglichkeit, eine Klasse mit einer von mehreren möglichen
Verhaltensweisen zu konfigurieren.
Denken Sie an Layout-Manager.
Das sind unterschiedliche Verhaltensweisen und da können Sie jetzt
hergehen, ich kann mal den benutzen, mal den benutzen, mal den dritten
benutzen.
Wenn Sie unterschiedliche Varianten eines Algorithmus benötigen,
Layout -Algorithmen sind auch Algorithmen, Wenn ein Algorithmus
Datenstrukturen verwenden soll, die den Klienten nicht bekannt sein
sollen, dann können Sie die in der Strategieimplementierung verbergen,
das muss also der Klientenprogramm nicht wissen.
Oder aber wenn Sie feststellen, dass Sie ein Programm schreiben, in
dem eine Switch-Anweisung wiederholt vorkommt, mit den gleichen Ästen.
Switch-Anweisung sagt, für den Fall, dass A passiert, dann das.
Falls B, dann das.
Falls C, dann das.
An anderen Stellen machen Sie das Gleiche.
Falls A, dann das.
Falls B, dann das.
Falls C, dann das.
Und so weiter.
Und Sie haben an mehreren Stellen immer wieder eine Switch-Anweisung
mit den gleichen Ästen.
Nicht notwendigerweise mit den gleichen Funktionen in den Ästen, aber
mit gleichen Ästen.
Dann gehen Sie her,
machen Funktionen aus all diesen Implementierungen der Äste und
stecken sie in die zugehörigen Klasse.
Also zum Beispiel nehmen Sie allen Code, der Fall C zuständig ist.
Also nehmen wir an, wir haben drei Stellen, an denen eine Switch
-Anweisung mit Fall C vorkommt.
Dann holen Sie sich den Code für Fall C heraus, aus diesen drei Switch
-Anweisungen, machen daraus drei Klassen und setzen dann den Code
jeweils als Methode eingekleidet in diese Klasse und das Gleiche
machen Sie für Fall B und für Fall C.
Und wenn Sie jetzt zu der Switch-Anweisung kommen, was müssen Sie
jetzt machen?
Sie brauchen jetzt eine Variable, die initialisiert ist mit der
richtigen Klasse, Klasse A, B oder C.
Die muss irgendwo gesetzt worden sein, möglicherweise direkt vor der
Switch -Anweisung und dann rufen Sie an dieser Switch-Anweisung, habe
ich den Fall, habe ich den Fall, habe ich den Fall, rufen Sie nur noch
die Methoden auf, die Sie haben wollen.
Das nennt man dieses Switchless-Programming und das geht mit der
Strategie wunderbar.
Also stellen Sie sich vor, ich schreibe hier meinen, ich hätte diese
Strategie nicht in meinem Containerdatenstruktur.
Ich müsste stattdessen
immer einen anderen Layout-Manager aufrufen.
Da müsste ich hier
statt SetLayout müsste ich jetzt die Funktion die Funktion DoLayout
umschreiben.
Die Funktion DoLayout macht wahrscheinlich nichts weiter, als den
Layout -Manager aufzurufen.
Aber dazu müsste ich jetzt was anderes schreiben.
Ich müsste jetzt hinschreiben, geschweifte Klammer auf, switch,
welchen Layout-Manager haben wir denn gerade?
Und da gibt es 25 davon, circa.
Und die Menge ist wachsend.
Da müssen Sie jetzt also eine 25-ästige Switch-Anweisung hinschreiben,
mit Fällen für jeden Layout-Manager, der jemals existieren wird.
Und den entsprechenden Code einflicken.
Das ist sehr umständlich, schwer zu warten.
Es ist besser, Sie machen es mit Strategie.
Denn da müssen Sie nur eine neue Strategie-Klasse hinzufügen.
Dann die Zuweisung, dafür sorgen, dass es eine Zuweisung gibt, auf
eine Variable.
Die die Instanz einer gewählten Klasse festhält und dann ganz normal
die Funktionen aufrufen, die es für alle diese Instanzen gibt.
Dann verschwindet diese Switch.
Sie haben gewissermaßen den Switch hineinverlegt in den
Polymorphie -Mechanismus, der sucht, welche Objekte haben und ruft
eine Methode auf, sagt, und welche Klasse ist das, zu welcher Klasse
gehört das eigentlich.
Diese Technik haben Sie damit benutzt, um Ihr Programm zu
vereinfachen.
Strategie wird also sehr häufig benutzt.
Da waren wir schon.
Ja.
Ja, natürlich könnten wir auch etwas anderes machen.
Wir könnten, jetzt gehe ich nochmal zurück zu meinem Container.
Ich könnte jetzt sagen, was ist die Alternative?
Ich habe ja hier auf einen ganz bestimmten Layout-Manager, auf einen
ausgewählten Layout-Manager delegiert.
Das Do-Layout macht nichts weiter aus, wie Layout-Container
aufzurufen.
Am Layout-Manager.
Alternativ könnte ich hier gehen, vergiss das alles, sondern mach hier
Unterklassen.
Da gibt es einen Container mit Border-Layout, einen Container mit Flow
-Layout, einen Container mit Grid-Layout, einen Container mit Box
-Layout, einen Container mit was weiß ich.
Jeden Container einfach Unterklasse des Containers selbst zu bilden.
Das würde funktionieren, nicht mal schlecht, denn die Funktionen und
Datenstrukturen, die die Layout-Manager alle gemeinsam haben, die
bleiben natürlich dann in dieser Oberklasse-Container.
Was ist der Unterschied in der Anwendung?
Welche Möglichkeit habe ich nicht mehr, wenn ich es so mache, dass ich
anstelle der Delegierung den Container Unterklassen bilde?
Lass mal nachdenken.
Auch die Leute, die auf dem PC schauen.
Alle ihr beiden da oben.
Wie wäre es?
Was ist der Unterschied?
Keine Ahnung.
Mitmachen, Leute.
Das hilft einfach.
Da sind noch ein paar, die fest auf dem PC schauen.
Was ist der Unterschied?
Sie wissen gar nicht, wovon ich rede.
Es ist einfach eine blöde Nutzung ihrer Zeit.
Ihre Zeit ist wertvoll.
Haben Sie sich schon mal überlegt, was eine wertvolle Zeit ist?
Machen Sie doch mit.
Ich bin doch keine Plaudertasche hier vorne.
Ich bin kein Fernseher.
Ich bin kein EM-Spiel, wo man irgendwas macht und nur hinschaut, wenn
es mal interessant wird.
Irgendjemand hat aber die Hand gehoben.
Ich weiß, was ich meine.
Das ist Nummer 1.
Es gibt noch einen zweiten Vorteil.
Wenn ich es so mache, kann ich dann natürlich auch bei anderen
Komponenten einsetzen.
Das ist natürlich absolut richtig.
Und ich habe unter Umständen viele, viele Container-Typen.
Es gibt noch einen.
Bei der Vererbung kann ich das nicht mehr.
Wenn ich das hier unter Klassenbildung, dann muss ich einen Border
-Layout -Container nehmen und dann bin ich an den Border-Layout
-Container gefesselt.
Da kann nichts anders.
Ich kann es nicht wechseln.
Mit der Situation kann ich sagen, dann wähle ich einen anderen.
Wenn es meine Layout-Situation erfordert.
Es kann ja sein, dass ich gerade bei unterschiedlichen Größen
verfügbaren Bildschirmen oder Ecken in meinem Bildschirm ein anderes
Layout wähle, weil es besser aussieht, wenn es klein ist und wenn es
groß ist.
Oder wenn viel drauf ist oder gerade wenig drauf ist.
Also machen Sie mit.
Ich bin für Sie da.
Beschwenden Sie Ihre Zeit nicht.
Machen Sie zu.
Ach so, Entschuldigung.
Ja, gerne.
Die eine Antwort hatten wir.
Ich kann die Layout-Manager, die Anordner woanders benutzen.
Wenn ich einen Container hier eine Unterklasse bilde und ich sage, ich
brauche jetzt einen Container, um meine Datenstruktur im Dateisystem
anzuzeigen und sage, ich möchte aber einen Layout-Container.
Einen Container mit Border-Layout, dann bin ich für immer daran
festgelegt.
Haben Sie schon mal gemerkt, dass Sie das Layout Ihres Datei
-Besuchers, mit dem Sie Dateien-Hierarchien anschauen, wechseln
können.
Das geht nur, weil die Leute es so implementiert haben.
Wenn ich gesagt hätte, ich zeige dir das an als einen Container mit
Flow -Layout, dann ist das Flow-Layout für alle Zeiten und einen
anderen zu machen, heißt dann, oh Gott, da muss ich jetzt einen
anderen machen und ich muss die Daten rüber retten und konvertieren
und hin und her und Pi-Pa-Po.
Es geht mit dieser Art und Weise viel eleganter.
Sie wechseln einfach den Layout-Container, bleibt der gleiche, die
Datenstrukturen bleiben alle gleich, Sie müssen keine Kopien anlegen,
Sie müssen nur noch mal Du-Layout aufrufen und die Sache stimmt.
Okay, noch Fragen dazu?
Genau erklären?
Ja.
Ist das nicht Zustand?
Sie meinen das Zustandsmuster?
Nein, das Zustandsmuster ist es nicht, weil ich habe nichts gesagt vom
Zustandswechsel.
Zustandswechsel sagt, es ist so ein ähnliches Muster, mit
Unterklassen, aber ich sage nicht, wie ich von Border-Layout zu Flow
-Layout komme.
Also warten Sie noch ein paar Tage, dann kommen wir zum Zustand.
Zum Zustandsmuster.
Und das Zustandsmuster kommt jetzt vor in der Liste, aber werden wir
überspringen, weil da eine sehr komplizierte Logik hinzukommt, die
hier nicht auftaucht.
Nämlich eine komplizierte Logik, wenn ich unterschiedliche Zustände
habe, die ich auch als Klassen auspräge, wie ich von einem in den
anderen Zustand komme.
Das kommt hier nicht vor.
Hier gibt es kein Ereignis, keine Eingabe, die sagt, springe ich auf
einen anderen Zustand und führe dabei eine bestimmte Aktion aus.
Also ich rede jetzt von Zuständen im Sinne von Zustandsautomaten wie
den Herald-Automaten.
Ja, ich brauche die Strategiestruktur, aber ich brauche noch mehr.
Aber glauben Sie mir, das werden Sie noch verfluchen, weil das ist
ganz schwierig, wenn wir so weit sind.
Okay, das erkläre ich jetzt nicht im Einzelnen, weil wir es nicht
brauchen jetzt gerade.
Okay, machen wir weiter.
Haben wir gesehen, das haben wir auch gesehen.
Den Dekorierer hatte ich schon erwähnt, da muss ich jetzt nicht mehr
viel dazu sagen.
Der Dekorierer sagt, Sie haben ein Objekt und Sie verschönern das mit
mehr, deswegen heißt es Dekorierer.
Das sieht also die Struktur so aus, dass Sie irgendeine Komponente
haben, das ist jetzt erstmal die Schnittstelle dafür.
Dann gibt es eine konkrete Komponente, das brauche ich immer, eine
gemeinsame Schnittstelle, die soll erweitert werden.
Ich will sie aber nicht für immer erweitert haben, das heißt, ich will
sie mal mit der Erweiterung haben, mal ohne.
Das heißt, ich muss sie jetzt in der Erweiterung vorschalten.
Das ist also so etwas wie ein Proxy.
Aber den Unterschied zu Proxy erklären wir auch gleich.
Es gibt da einen feinen Unterschied.
Dekorierer sagt die gleichen Methoden und dann gibt es einen konkreten
Dekorierer A und B.
Die müssen natürlich diese Methoden auch implementieren,
möglicherweise einfach nur, indem sie dorthin delegieren.
Aber sie können auch neue Methoden hinzufügen.
Dekorierer erlaubt mir das.
Natürlich könnte ich hergehen und sagen, ich mache die super
Komponente, die alles kann, aber manchmal will man das nicht, weil man
die Komponente auch in dieser einfachen Form haben will und manchmal
in der erweiterten Form.
Also bauen wir uns eine zusätzliche Komponente, die zusätzliche
Schnittstelle hat und fertig.
Also zum Beispiel, Sie hätten ein Textfeld und dann sagen Sie, naja,
das Textfeld ist ja ganz nett, aber ich hätte gerne noch ein paar
Rollbalken davor, damit ich den Text rumziehen kann, dann bauen Sie
sich einen Rollbalken-Dekorierer.
Und dann sagen Sie, ja gut, schön, aber ich hätte auch noch gerne
einen schönen Rahmen drumherum, dann bauen Sie sich noch einen Rahmen
-Dekorierer dazu.
Also das ist der Ursprung, dann das hinzu, dann das hinzu.
Das könnte also so aussehen, wie vorher, Sie haben eine visuelle
Komponente, die hat nur die Funktion Zeichne.
Mehr brauchen wir jetzt nicht zur Diskussion, die Textanzeige hat die
Funktion Zeichne.
Der Dekorierer sagt, ich habe auch mal eine Funktion Zeichnen, aber
die delegiert lediglich an meine Komponente, das sagt er, ich bin ein
Aggregat, ich habe eine Komponente, ich rufe an diese Komponente die
Zeichne -Funktion aus, das macht noch nichts Neues, gar nichts, aber
jetzt kommen der Scroll- und der Rahmen-Dekorierer und die sagen, ich
habe jetzt eine Zeichen-Funktion, die sagt, ich rufe dazu die Super
-Funktion auch von Zeichnen, das ist natürlich das Original-Zeichnen
und dann füge ich den Rahmen-Zeichnen hinzu und der macht irgendwas
mit dem Rollbalken.
Und schon habe ich das Ding erweitert, sodass es auch einen Rahmen
zeichnen kann und wenn ich es so nicht will, dann lasse ich das Ganze
weg.
So, Unterschied, Dekorierer fügt Objektfunktionalität hinzu, ohne das
Subjekt zu ändern, kann Subjektschnittstelle erweitern, das kann er
erweitern, Proxy kann das nicht.
Das Proxy macht eine Art Zugriffssteuerung, sagt, ist das erlaubt oder
nicht, oder ich zähle es, oder ich puffre dazwischen, da gibt es
mehrere Anwendungsbereiche, aber das Proxy soll genauso verwendbar
sein, wie das Original.
Das ist beim Dekorierer nicht der Fall, da kommt etwas hinzu.
Das heißt, ich kann den nackten, undekorierte Klasse nicht benutzen,
im Kontext, wo ich ein dekoriertes Element erwarte, denn da werden ja
die zusätzlichen Funktionen und Attribute aufgerufen.
So, das war der Dekorierer und damit sind wir eigentlich fertig mit
den Mustern, mit denen ich mehrere Varianten bilden kann.
Ich sagte Ihnen ja, wir haben jetzt eine Klasse von Mustern, die
zeichnen sich dadurch aus, dass sie unterschiedliche Varianten mir
erlauben.
Denken Sie gerade an die Strategie, die Strategie kommt da sehr häufig
vor, aber auch der Besucher.
Der Besucher sagte, ich habe unterschiedliche Varianten von Code, den
ich auf ein Kompositum schicke, zum Abwandern.
Das Kompositum selbst ist natürlich auch ein Variantenmuster, denn es
hat Blätter und Komposita, zusammengefügte Objekte.
Was hatten wir noch?
Schablonenmethode.
Schablonenmethode sagt auch, ich habe einen komplexen Algorithmus, den
muss ich aber spezialisieren für unterschiedliche Situationen.
Wieder mehrere unterschiedliche Varianten und die Fabrikmethode ist
Ähnliches.
Das waren die einzelnen Muster hier.
Jetzt gehen wir über in solche, die sich mit dem Zustand von Objekten
generell befassen.
Mit dem Zustand von Objekten.
Ein ganz einfaches, beliebtes, ist das Einzelstück.
Es ist ganz einfach, es ist einfach nur eine Art Programmiertrick.
Das Einzelstück sagt, ich will sicherstellen, dass für jede Klasse,
für eine gegebene Klasse, Entschuldigung, genau eine Instanz gibt.
Ich erkläre Ihnen gleich, warum das interessant sein könnte.
Aber ich würde also jetzt versichern, dass jedes Mal, wenn ich einen
Konstruktor an dieser Klasse aufrufe, ich immer das gleiche Objekt
kriege.
Nie mehrfache Objekte, weil ich natürlich verhindern will und wenn ich
das verhindere, erkläre ich gleich.
Zu implementieren, ganz einfach, Sie haben hier eine Klasse, die nenne
ich jetzt mal Einzelstück.
Die hat einen Konstruktor.
Den Konstruktor überschreiben Sie, den machen Sie erstmal privat,
damit er von außen nicht benutzt werden kann, denn der wird jedes Mal
ein neues Objekt anlegen und das wollen Sie nicht.
Stattdessen haben Sie ein statisches Attribut, das heißt einziges
Exemplar und das nennen Sie Einzelstück und das ist auf Null
initialisiert.
Am Anfang ist da nichts drin, aber sobald Sie das erste Mal den
Konstruktor aufgerufen haben, erzeugen Sie ein einzelnes Objekt und
weisen es dem Einzelstück zu und dann sagen Sie, aha, das ist das, was
ich jedes Mal zurückgebe, wenn weitere Aufrufe an den Konstruktor
kommen.
Und hier ist die Logik dafür, gib Einzelstück, ist jetzt der neue
Konstruktor.
Wenn man die Programmiersprache erlauben würde, den Konstruktor zu
überschreiben, dann könnte ich Sie natürlich auch Einzelstück nennen.
Statische Methode, liefert mir ein Einzelstück und der Code dafür ist
einfach, falls dieses Einzelexemplar noch Null ist, naja, dann haben
wir noch keines, dann machen wir eins, also rufen wir dieses Original
Konstruktor auf, merken uns das im einzigen Exemplar und geben es
zurück.
Fertig.
Das ist alles, was hinter dem Ding steckt.
Leicht zu verstehen.
Die Frage ist nur, wozu brauche ich das?
Also, was Ihr Kollege gesagt hat, war, wenn man Daten speichern möchte
in einer Anwendung und sicherstellen will, dass sie nicht kopiert
werden und man möchte, dass alle Systemteile darauf nur immer auf
dieses Datenobjekt oder Menge von Daten zugreift.
Ja, das stimmt.
Es gibt sogar ein konkretes Beispiel dafür, auch wieder in Java, mit
dem Sie sich später auseinandersetzen werden müssen.
Das ist, glaube ich, Kit
oder Graphic Context.
Da gibt es also Ne, ich muss noch mal nachschauen.
Also es gibt jedenfalls eine Klasse, die sehr, sehr viel enthält.
Zum Beispiel die kompletten Schriftsätze in allen Größen mit allen
Varianten, Kursiv und so weiter.
Enthält alle Farben, alle Schriftfarben, alle Hintergrundfarben, jede
Menge von Daten.
Und man braucht so ein Objekt jedes Mal, wenn man zum Beispiel Farben
ändern möchte oder Schriftgrößen festlegen möchte oder so etwas.
Dazu muss man sich so ein Bausatz anlegen.
Wenn man jetzt aber eine größere Anwendung hat, weiß man nicht, ob so
ein Bausatz bereits angelegt wurde.
Vielleicht gibt es dann mehrere Komponenten, mehrere Subsysteme, wo
Leute auch schon ein Ding angelegt haben und der verbraucht sehr viel
Speicherplatz.
So will man davon kein Duplikat haben.
Man will aber auch nicht verhindern, dass jemand sagt, gib mir den
Bausatz jetzt.
Ich brauche den jetzt.
Dann sagt man einfach, gut, das lösen wir mit per Einzelstück.
Du kannst so tun, als wärst du allein auf weitem Felde.
Wenn aber schon ein anderer Teil des Systems das Ding schon angelegt
hat, dann kriegst du genau das.
Es geht also sehr elegant über den Konstruktor, der jetzt ein bisschen
mehr Logik enthält.
Der sagt, bin ich schon mal angelegt worden?
Wenn ja, kriegst du das, wenn nicht, lege ich es an.
Okay, weiter Einzelstück.
Wenn von einer Klasse nur eine einzige Instanz geben darf, diese
Instanz entkennt einen bekannten Stellen, nämlich über die Klasse
zugänglich gemacht werden soll.
Wenn es schwierig oder unmöglich ist, festzustellen, welche Teile der
Anwendung, wir denken immer an Projekte mit vielen Personen, die erste
Instanz erzeugt, dann müsste ich jetzt rum fragen, hast du schon?
Bin ich auch sicher, dass dein Code vor meinem läuft?
Stattdessen sagen sie, wenn ich es brauche, dann hole ich es mir und
wenn es es dann schon gibt, hat es sich die Klasse gemerkt.
Und zwar, Sie haben ja gesehen, das war ein statisches Element, das
heißt, das sitzt in der Klasse, nicht im Objekt.
Okay.
Gut, Sie können auch noch Unterklassen bilden davon, das geht auch
noch, das ist kein Problem.
Gibt es Fragen dazu?
Zurück zum Einzelstück.
Gut, jetzt kommt das Fliegengewicht, Flyweight, wie beim Boxen.
Da ist so etwas ähnliches.
Sie haben eine große Menge von Objekten, von denen es aber Duplikate
gibt und Sie wollen nicht alle Objekte wirklich speichern, weil der
Speicherplatz dafür nicht reicht.
Ich springe zuerst zum Beispiel.
Also angenommen, Sie entscheiden sich, Sie wollen jetzt den
allerbesten Textformatierer bauen, den es gibt auf der Welt und der
bedeutet, dass Sie jedes einzelne Zeichen jedes einzelne Zeichen als
Objekt modellieren.
Das heißt also, Sie haben so etwas wie Charakterklasse, Ihre eigene
Charakterklasse, da kommt Größe rein, da kommt natürlich der
Zeichencode rein und die Schriftart und die Farbe und ob es kursiv ist
oder nicht und so weiter.
Das machen Sie für jedes einzelne Zeichen.
Das ist natürlich aufwendig, denn die einzelnen Buchstaben kommen sehr
häufig vor.
Das am häufigsten vorkommendste Buchstabe ist das E zum Beispiel.
Also angenommen, Ihr Text enthält in einer Zeile hier das Wort
Beispiel und jetzt können Sie entweder daraus eine Schlange machen und
sagen Objekt B, Objekt E, Objekt I, Objekt S, Objekt P, Objekt I,
Objekt E und so weiter.
Das Fliegengewicht geht da anders vor.
Das Fliegengewicht sagt Ich nehme statt eines Objektes hier nehme ich
einfach nur den Schlüssel dieses Objektes, nehme ich den Zeichencode
dafür also den Unicode, 16 Bit oder sowas und die übrige Information,
nämlich die Größe, der Zeichensatz, die Farbe, der Hintergrund,
Kursiv, Fett, hochgestellt, tiefgestellt, unterstrichen,
durchgestrichen, überstrichen, was alles gibt.
Das speichere ich nicht mit dem einzelnen Buchstaben, sondern ich
speichere es in den größeren Einheiten, zum Beispiel in der Spalte,
weil in der Spalte meistens, die Ausnahmen müssen wir dann
berücksichtigen, meistens der gleiche Schriftgröße, gleiche
Schriftart, gleiche Kursiv oder nicht Kursiv, gleiche Farbe und so
weiter gilt.
Ich muss nur gelegentlich umschalten können.
Das heißt also, ich mache mir jetzt eine Datenstruktur, in der es
einen intrinsischen Zustand gibt und einen extrinsischen.
Der intrinsische Zustand ist immer der Zeichencode in diesem Fall oder
der Code, den ich für jedes einzelne Objekt brauche, weil es sich
damit von anderen unterscheidet und der extrinsische Information ist
die, die ich normalerweise immer nur wiederholen würde und die ziehe
ich dann in eine umfassendere Datenstruktur raus.
Wenn es zum Beispiel die ganze Spalte in der gleichen Farbe, der
gleichen Schrift, der gleichen Größe, der gleichen Schriftart und so
fort wäre, dann steht hier vorne, in dieser Spalte haben wir Farbe
blau, Times Roman Größe 20, Normalschrift, keine Unterschriftreichung,
keine Übersteuerung, kein Tiefstellen, kein Nichts.
Und dann spare ich mir diese Information.
Wenn ich jetzt hergehe und sage, okay, aber jetzt kommt das Wort
Beispiel da inzwischen im Text vor und das möchte ich jetzt kursiv
setzen.
Dann muss ich mir eine Datenstruktur bauen, zusätzlich bauen, die
sagt, und ab hier ist es anders.
Das heißt, ich muss in meiner Datenstruktur, entweder in der Zeile
oder auch in der Spalte, eine Ausnahme anlegen können, die sagt, aber
hier ist ein anderes Objekt jetzt.
Das ist jetzt nicht mehr Zeichenreihe mit dem Zeug, zusätzlichen
Attributen von oben, sondern jetzt ist es anders.
Das ist die Idee dahinter.
Das haben wir gesagt.
Also sieht das dann, wenn man sich dann tatsächlich objektorientiert
machen möchte, sieht das jetzt folgendermaßen aus.
Wir brauchen wieder eine Fliegengewicht-Fabrik, aber das ist jetzt
nicht so wichtig.
Es gibt jetzt Fliegengewichte zweierlei Art.
Das eine ist das konkrete Fliegengewicht, das einen extrinsischen
Zustand hat.
Das heißt also, nicht alle Informationen da ist.
Im Extremfall wäre das nur Zeichencode.
Und das sagt, ich habe Operationen und einen intrinsischen Zustand, in
unserem Fall wohlgemerkt, Zeichencode, aber es gibt Operationen, die
ich sonst noch ausführen kann, was weiß ich, Schriftgröße vergrößern.
Dazu brauche ich aber den extrinsischen Zustand und den muss ich dann
per Parameter irgendwie liefern können aus der großen Datenstruktur.
Und dann gibt es das konkrete genutzte Fliegengewicht, das ist
eigentlich kein Fliegengewicht mehr, denn es enthält alles.
Also ich bin jetzt so.
Ich habe den gesamten Zustand und die Operationen, die laufen dann im
extrinsischen Zustand, der stammt aber aus dem Gesamtzustand.
So, jetzt brauchen wir noch den Konstruktor.
Der sagt also, der Konstruktor sagt, falls das Fliegengewicht bereits
existiert mit einem bestimmten Schlüssel, dann gibt es zurück.
Dann gibt es so ein Objekt schon, nämlich wahrscheinlich so eines.
Das ist ähnlich wie beim Einzelstück.
Andernfalls erzeuge ich ein neues Fliegengewicht, füge die Mengen der
Fliegengewichte ein, gebe dieses neue Fliegengewicht zurück.
Das ist alles, was ich jetzt ein bisschen ungenau gesagt habe, ist,
wie ich den extrinsischen Zustand, den zusätzlichen Zustand bekomme.
Und wie gesagt, das hängt ganz von der Anwendung ab, wo ich so den
Zustand aufbewahre, der eigentlich normal ist, der für alles gilt, es
sei denn, ich habe eine Ausnahme, zum Beispiel über ein getrennt
genutztes Fliegengewicht.
Also ich könnte es zum Beispiel so machen, wenn ich so eine Spalte
habe, wie ich vorher hatte, da habe ich die ganze Information über
Schriftsätze drin, wenn ich dann zu dem Wortbeispiel komme, das jetzt
in Rot und Groß und Kursiv und in einer ganz anderen Schriftart
gesetzt wird, dann fange ich hier vorne an mit einem neuen getrennt
benutzten Fliegengewicht, das alles enthält und dann sagt es so, wenn
du da bist, dann schalte ich um.
Und da gilt alles andere, das heißt, die Dinge hier bleiben
Fliegengewichte, wenn dann da hinten der nächste Buchstabe kommt, der
jetzt nicht mehr Kursiv und in Rot und Größe sein soll, dann sage ich
und jetzt schalte ich wieder auf den Normalzustand da oben und gehe
weiter.
Das erfordert, dass ich den Text immer von oben nach unten natürlich
abarbeite und nicht irgendwo in der Mitte hineinspringen kann, denn
sonst habe ich so einen Wechsel übersehen.
Also, nochmal, wozu brauche ich das?
Wenn der Anwender eine große Menge von Objekten verwendet wird, wenn
Speicherkosten allein aufgrund der schieren Anzahl dieser Objekte zu
groß sind, denken Sie an die Diplomarbeit, die Sie mal, die
Masterarbeit, die Sie mal schreiben, das sind 80, 100 Zeiten, da
kommen schon wirklich viele Objekte vor, die wollen Sie nicht als
Einzelobjekte in Java darstellen.
Wenn ein Großteil des Objektszustandes in den Kontext verlagert werden
kann, in unserem Fall war es die Spalte oder den Absatz oder das
Kapitel und damit extrinsisch gemacht werden, also herausgezogen und
nur einmal gespeichert wird.
Viele Gruppen von Objekten durch relativ wenig gemeinsame genutzte
Objekte ersetzt werden können, sobald einmal der extrinsische Zustand
entfernt wurde, dann haben Sie nur noch gleichförmige Objekte, von
denen brauchen Sie nur noch einen Code aufzubewahren, den Code zum
Beispiel für den Zeichen und die Anwendigkeit von der Identität der
Objekte nicht von der Identität der Objekte abhängt.
Also ich muss nicht wissen, das ist das und nur das E, es ist halt ein
E, so wie die anderen E's auch.
Okay, das war das Fliegengewicht.
Vom Konzept her, simpel denke ich, die Idee ist, Zustand raus von
wiederholt benutzten Objekten, die sehr viel Zustand ist, der in Ihnen
gemeinsam ist, den hole ich mir in eine übergeordnete Datenstruktur
heraus.
Und sparen wir damit Speicherkosten.
Fragen dazu?
Okay, noch ein einfaches, und zwar heißt es Memento, das ist eine Art
Zwischenspeicher.
Und zwar ist das so, wenn Sie sagen, ich habe ein Objekt in meinem
Programm, ich werde es jetzt gleich verändern, aber es sollte möglich
sein, dass ich mich, dass ich zurückschalte, wenn sich irgendwann ein
Fehler ergibt oder sonst irgendwie ein Rücksetzbefehl käme.
Gerade beim Rücksetzen ist das wichtig, dass Sie die Zustände der
Objekte retten.
Und das gerettete Objekt, das den Zustand enthält, nennt man das
Memento, das Erinnerungsstück.
Das sieht so aus, Sie haben ein Originalobjekt, das ist dann jetzt ein
Urheber, das ist das Originalobjekt, das hat einen Zustand, egal wie
groß der ist, ist mir wurscht.
Und ein Memento, in diesem Memento können Sie den Zustand retten.
Naja, da gibt es einfach eine ganz einfache Methode, die heißt sichern
des Zustandes, indem es ein Memento zurückliefert und das ruft einfach
den Konstruktor von Zustand auf.
Entschuldigung, wir haben eine, den muss ich anschauen, ein neues
Memento erzeugt und übergibt ihm den Zustand.
Das ist natürlich arg schematisch gedacht.
Hier stehen unter Umständen mehrere Variablen, vielleicht Verweise auf
andere Objekte, vielleicht Listen, Container und so weiter.
Wir müssen natürlich alle kopiert werden und in dieses Memento
abgelegt werden.
Wenn es allerdings Objekte, Attribute gibt, die aus anderen errechnen
kann, zum Beispiel das Alter aus dem Geburtsdatum, dann muss ich das
Alter nicht speichern, sondern wenn ich das dann wiederherstelle, beim
Wiederherstellen dieses Ursprungs, dann muss ich eben die Logik da
einbauen, in dieses restauriere, dass das fehlende Attribute
ausrechnet.
Ich kann also jetzt die Logik, ein Memento zu erzeugen, stecke ich in
die Originalklasse mit hinein, kann damit jederzeit von einem Objekt,
das davon abgeleitet ist, den Zustand sichern.
Den Zustand kann ich übrigens generisch sichern, weil ich ja in Java
per Reflexion abfragen kann, welche Attribute hier drin sind.
Ich kann hier Unterklassen bilden, wenn ich hier das sichere Zustand
gut implementiert habe, muss ich nichts mehr machen.
Und gebe dieses Memento dann einem Aufbewahrer.
Ein einfaches Beispiel ist, Sie sind in einem Spiel, haben endlich
diese Stufe 13, würden Sie gerne erreichen, aber jedes Mal, wenn Sie
Fehler machen, fliegen Sie raus aus dem Spiel, müssen von vorne
anfangen, müssen sich durch Stufe 1 durchkämpfen, durch Stufe 2.
Und am Schluss sagen Sie, okay, dann muss ich mir jemanden heuern aus
Indien, der das für mich macht.
Sie würden gerne auf der Stufe 10 dann weitermachen, obwohl Sie in
Stufe 11 versagt haben.
Was mache ich da?
Mit dem Memento natürlich.
Ich muss natürlich den gesamten Spielzustand speichern können.
Und hier ist es mal aufgezeichnet für einen absolut trivialen
Spielzustand, nämlich nur die Ebene sich zu merken.
Aber wie gesagt, das ist symbolisch gemeint.
Sie haben das Klientenprogramm.
Das will sagen, ich will den Spielstand sichern, indem ich sicheren
Spielstand aufrufen kann.
Dann sollte ein Memento zurückkommen.
Wenn ein alter Spielstand wieder hergestellt werden soll, dann rufe
ich Spielpunkt, restauriere Spielstand.
Das ist die Logik, die ich haben will.
Dazu gebe ich den Spielstand her.
Der muss ja natürlich irgendwo auch bewahrt worden sein.
Hier habe ich den Spielstand gerettet.
Spiel und ich will nur den ganzen Zahl hier merken.
Spiele-Memento, sicher Spielzustand ist natürlich total trivial hier.
Ich nehme das Spiele-Memento her, rufe einen Konstruktor auf und gebe
ihm den augenblicklichen Spielstand und der ist nur eine ganze Zahl.
Und das Restaurieren funktioniert natürlich genauso im umgekehrten Weg
und das hier gibt den Spielstand mir zurück.
Das ist also die Idee dabei.
Und das haben wir schon.
Es gibt noch viele andere.
Also Sie verschieben was auf Ihrem Bildschirm und dann sieht das Bild
so aus.
Dann schieben Sie es zurück und dann erscheint diese Kante plötzlich
an der falschen Stelle.
Was Sie gerne hätten ist, dass das Originalbild erzeugt wird.
Das machen Sie, indem Sie hier diese Objekte, die Sie gleich verändern
oder zumindest das Ding hier, das verändern Sie und das haben Sie
verändert.
Das haben Sie verschoben und das haben Sie auch gedreht und auch
verschoben.
Dann merken Sie sich zwei Mementos.
Und wenn der Benutzer jetzt auf rückgängig machen drückt, einmal, dann
wird das erste Memento aufgespielt und wenn Sie das zweite Mal auf
Rückstellen setzen, dann wird das zweite Memento aufgespielt und dann
sieht es wieder so aus.
Das macht man tatsächlich so.
Also Ihr Rückgabepfeil bewirkt, dass die Änderungen, die Sie
ausgeführt haben, zurückgespielt werden und die muss natürlich
aufbewahrt worden sein, der Ursprungszustand.
Und das kann ich auf Objektebene machen.
Also wann brauche ich das?
Wenn ich eine Momentaufnahme meines Programmzustandes habe oder ein
Teil und den zwischenspeichern will und später wieder herstellen
möchte.
Wenn Sie eine direkte Schnittstelle zum Ermitteln des Zustandes nicht
mögen.
Aber das ist ein anderes Detail.
Also ich hatte Ihnen jetzt gesagt, zum Rücksetzen ist das nützlich.
Das kann auch nützlich sein zum Wiederherstellen meines Programmes,
wenn mein Rechner abstürzt.
Kennen Sie auch, ja?
Wir haben zwei Stunden an Ihrer Dokumentation gearbeitet, an einer
Textdatei und plötzlich schmiert Ihr Rechner ab und die ganze Arbeit
ist futsch.
Wäre doch gut gewesen, wenn Sie zwischendrin mal gesichert hätten.
Auf die Platte.
Dann können Sie mit dem Zustand weiterarbeiten, haben vielleicht noch
10 Minuten verloren.
Das macht man übrigens so ähnlich bei sehr lang laufenden Programmen,
zum Beispiel numerischen Programmen auf Supercomputern, die Tage und
Wochen laufen und gelegentlich ein Absturz passiert.
Nicht, weil das Programm kaputt ist, sondern weil irgendwas anders
falsch geht.
Irgendwie ein Kabel herausrutscht oder ein Rechner streikt oder eine
Platte abraucht oder so.
Sie haben tausende von Rechnern.
Was Sie machen, ist, Sie sichern den Zustand der Rechnung, der
Berechnung gelegentlich, sagen wir jede Stunde.
Wenn dann der Rechner abschmiert, dann können Sie sagen, ja gut, dann
haben wir vielleicht die letzte Stunde verloren, aber dahinter wissen
wir, was wir hatten und von der Stelle arbeiten wir weiter.
Auch an der Stelle braucht man dieses Wiederherstellen des
ursprünglichen Zustandes.
Wir wissen jetzt, wie wir das objektorientiert schön machen können.
Das war das Memento.
Sie sehen, auch das ist allgemeine Verwendung von Zustand.
Nämlich den Zustand, beliebigen Zustand eines Objektes sichern.
Fragen dazu?
Es wird immer leichter, denn jetzt kommt der Prototyp und den haben
Sie schon in Java eingebaut über die Funktion Clone.
Klonieren eines Objektes.
Und zwar sieht das etwas so aus.
Wir brauchen immer ein Klientenprogramm, das es benutzt.
So haben wir hier einen Prototyp.
Die Idee des Prototyps, der hat die Funktion Kloniere oder Klone oder
Clone oder wie Sie es nennen wollen oder dupliziere, repliziere.
Gibt es einen konkreten Prototyp und alles, was die Klonoperation
macht, sie ergibt in ein neues Objekt, das genau die gleichen Daten
enthält, wie das alte.
Nicht besonders schwierig zu verstehen, nicht wahr?
Aber ist manchmal nützlich.
Und wie gesagt, diese Klonoperation ist Ihnen von Java bereits
vorgegeben.
Sie müssen sie nicht mal mehr selber programmieren.
Aber es gibt da was zu beachten.
Und zwar nehmen wir mal an, wir haben hier einen Kalender.
Der hat zwei Listen drin und AL2, die Adressliste, wird von der ersten
durch Klon erzeugt.
Wir haben eine Arraylist und ein Datum.
Gibt das alles für Klonier-Operationen.
Und Sie sehen jetzt, dass ich zunächst diese erste Liste AL.klon
aufrufen und dann auf AL2 zuweise.
Und dann nochmal eine Vorschleife habe, die nochmal kloniert.
Und zwar, sie geht durch eine Adressliste durch, nämlich die hier und
fügt die klonierten Elemente hinzu.
Und der Grund dafür ist, dass das Klonieren in Java eine seichte Kopie
anbietet.
Eine seichte Kopie anfertigt.
Das heißt, wenn Sie eine Liste haben von Elementen und Sie rufen auf
dieser Liste Klonieren auf, dann wird diese Liste kopiert, aber nicht
die Referenzen.
Die Referenzen werden einfach übernommen.
Das heißt, die zwei Listen zeigen auf die gleichen Datumselemente.
Deswegen werden die dann in einem zweiten Schritt kloniert.
Also beim Klonieren, beim Klonieren-Aufruf von Java darauf achten,
dass das nur eine seichte Kopie erzeugt, wenn Sie eine tiefe Kopie
brauchen.
Dann müssen Sie das noch extra programmieren.
Dummerweise gibt es keine Klonierfunktion, die tiefe Kopien anfertigt.
Kann man natürlich machen.
Allgemein sogar per Reflexion zu baubar, ist aber gefährlich, weil Sie
ja dann unter Umständen riesige Datenbereich, alles was erreichbar ist
von einem Objekt, das Sie klonieren wollen, aus alles kopiert wird.
Vielleicht wollen Sie doch nicht so weit gehen.
Okay.
Die Methode klon, erstellen zu einer seichten Kopie, werden alle
Attribute kopiert, einschließt der Referenz auf andere Objekte, die
referenzierten Objekte selbst nicht.
Das heißt, Gleichheit erster Stufe.
Wann benutze ich dieses Teil?
Haben Sie vielleicht noch nicht benutzt.
Wann benutzt es eigentlich im Wesentlichen?
Wenn Sie Objekte erzeugen wollen, ohne zu wissen müssen, welche Klasse
ich dazu brauche, sondern einfach von einer anderen Stelle die Dinge
kopieren.
Ich will keine Konstruktoren wissen, ich will da nicht die Klassen
wissen, was ich da alles brauche.
Gib mir einfach deine Objekte als eine Liste oder eine Datenstruktur,
ich mache mir eine Kopie davon.
Es ist viel einfacher, als sie meistens neu aufzubauen.
Zum Neuaufzubauen brauchen Sie Logik, die Sie nicht haben vielleicht.
Okay.
Außerdem, selbst wenn Sie die Logik haben, aber das Aufbauen der
Objekte Rechenarbeit erfordert und diese Rechenzeit wollen Sie sparen,
dann sagen Sie, na gut, dann mache ich mir eine Kopie von dem Objekt,
anstelle die interne neu zu berechnen.
Solche Objekte gibt es manchmal, aber nicht allzu oft.
Man sieht dann aber auch oft, wenn man sehr viele Objekte hat, dass
man Klassenhierarchien braucht von Fabriken, um die Objekte zu
erzeugen, sondern sich...
abstrakte Fabrik hatten wir letztes Mal schon besprochen.
Das nächste ist, dass man auch Prototypen benutzt, wenn es zwar viele
Attribute sind, die in dem Prototyp stecken, aber eigentlich nur
wenige Kombinationen gebraucht werden an bestimmten Stellen.
Dann ist es oft leichter, einfach ein Vorrat an Vorlagen zu erstellen
und sie dann neu zu kopieren.
Ähnlich wie Sie in Powerpoint oder sonst wo Vorlagen haben, anstelle
das alles neu zu zeichnen.
Geben wir dieses Vorbild, diese Vorlage und ich arbeite einfach mit
der Vorlage weiter und natürlich bekommen Sie da eine Kopie aller
wesentlichen Attribute, eine Präsentation zu machen.
Okay, das war das.
Und jetzt gibt es zum Klonieren noch Fragen?
Zustand, das glaube ich hatten Sie erwähnt, das meinten Sie, bespreche
ich jetzt nicht.
Wir kommen dazu später, wenn wir über die Implementierung von
Zustandsautomaten sprechen.
Es sieht in der Tat aus wie Strategie, aber da fehlt einiges.
Darum mache ich das jetzt nicht.
Da fehlt viel.
So, das waren schon unsere Zustandshandhabungsmuster.
Was kann ich mit dem Zustand von dem Becken denn alles anstellen?
Ich kann klonieren, ich kann sichern, ich kann sicherstellen, dass es
nur einmal gibt und sowas.
Diese Gruppe ist nicht besonders kräftweitläufig.
Die Steuerungsmuster sind ein bisschen besser.
Und das erste ist das Kommandomuster und das ist jetzt wieder wichtig.
Denn das kommt häufig vor und wird oft nicht so richtig verstanden.
Da geben wir uns ein bisschen mehr Zeit.
Die abstrakte Definition ist so, dass ich sage, ich will ein Objekt
haben, das eigentlich etwas ausführen soll, aber ich will den Code
noch nicht ausgeführt haben.
Ich will ihn später ausgeführt haben, aber ich will das Objekt und die
Parameter, die dazugehören, schon mal fertig konfigurieren.
Klingt jetzt komisch, aber ich brauche das Kommando zum Beispiel
permanent bei grafischen Benutzeroberflächen.
Da sage ich so etwas wie, ich brauche ein Kommando, um ein Dokument
anzulegen oder die Schriftgröße zu ändern oder zu sichern oder was es
auch ist.
Dieses Kommando muss ich schreiben.
Ich kann aber nicht vorhersagen, wann es aufgerufen wird, denn der
Benutzer entscheidet, wann gesichert wird, wann ein Dokument geöffnet
wird, wann eine bestimmte Operation ausgeführt wird.
Das heißt also, ich muss mir ein Kommando vorbereiten.
Ich darf es nicht ausführen, weil ich nicht weiß, wann es ausgeführt
werden soll.
Ich kann den Code zwar hinschreiben, aber ich kann nicht sagen, wann
es kommt.
Also muss ich mir das Kapseln, also ein Objekt, und ein Menü anhängen,
sodass wenn ich an den entsprechenden Menü-Eintrag klicke, jetzt will
ich genau dieses Kommando ausgeführt haben.
Dazu brauche ich dieses Kommando.
Deswegen diese vielleicht unverständliche Formulierung zunächst mal.
Es ist ja möglich, Klienten verschiedene Anfragen zu parametrisieren,
Operationen in eine Warteschlange zu stellen, ein Logbuch zu führen,
Operationen rückgängig zu machen.
All das kann ich damit anstellen.
Die Nummer dafür sind Kommando, Aktion und Transaktion.
Und jetzt zeige ich Ihnen gleich das Beispiel anstelle des abstrakten
Bild.
Das Beispiel ist so.
Sie haben eine Anwendung, zu der können Sie ein Dokument hinzufügen.
Das ist natürlich ein Dokumentenformatierer.
Diese Anwendung enthält ein Menü.
Dieses Menü enthält eine Funktion, mit der Sie einen Menü-Eintrag
hinzufügen können.
Das ist der Menü-Eintrag hier.
Also ein Menü besteht jetzt aus mehreren Menü-Einträgen.
Dieses Menü-Eintrag enthält einen oder mehrere Befehle.
Jeder Befehl hat eine bestimmte Funktion, eine einzige Funktion, die
heißt führe aus oder execute oder run oder do oder tu es oder mach
schon oder was Sie auch wollen.
Diese Funktion, nur diese Funktion muss ich wissen.
Das ist jetzt zunächst mal nur eine Strategie, denn es gibt mehrere
Befehle und der Einfügebefehl selbst zum Beispiel, hier ist die
Führerausfunktion ausgeführt.
Ich muss an dem Dokument die Einfügefunktion aufrufen.
Das ist ja absolut primitiv, aber natürlich können hier mehr Code
stehen.
Wir brauchen nur immer das Beispiel, es muss auch passen.
Ich habe auch gleich eine etwas verlängerte Funktion, dass es nicht so
lächerlich daherkommt.
Was Sie jetzt erreichen wollen ist, wenn Sie auf Menü-Eintrag klicken,
dass diese Funktion ausgeführt wird, die Sie vorher als Datum an den
Menü -Eintrag geklebt haben.
Und das geht letztendlich ganz einfach, wenn Sie jetzt
auf diesen Menü-Eintrag tatsächlich mit der Maus klicken, dann läuft
im Hintergrund eine Funktion ab, die sagt, und was ist dieses Ding,
was hier dran hängt an dem Menü-Eintrag?
Ich muss nach dem Befehl schauen und ich weiß, dass es eine Funktion
gibt, die heißt Ausführe aus.
Und die rufe ich jetzt einfach auf.
Ich muss also hier der Code für geklickt heißt nur Befehl ausführe
aus.
Also das heißt jetzt nicht geklickt im Java zum Beispiel, sondern
heißt dann irgendwas wie Maus pressed oder Maus down oder Maus up.
Die Maus down oder Maus up Funktion müssen Sie, oder Maus klickt
Funktion müssen Sie implementieren und die tut nichts weiter als
sagen, naja, was ist denn die ausführende Funktion, die ich jetzt
brauche?
Do, execute.
Die führe ich jetzt einfach aus und den Punkt, den ich eben machen
wollte, ist, dass Sie diesen Befehl geschrieben haben, in ein Objekt
gekleidet haben, Sie brauchen eine Instanz davon und diese Instanz
hier den Menü-Eintrag angeklebt haben.
Das können Sie sogar mehrere Kommandos ankleben, wenn Sie wollen, dass
ein einziger Mausklick gleich eine Reihe von Befehlen ausführt.
Auch das geht.
Das ist also die Ursprungsidee.
So, jetzt gehen wir zurück auf den Abstrakten.
Auf Anordnung, die sagt, es gibt eine Strategie oder eine Oberklasse,
eine Schnittstelle, Befehl.
Dafür gibt es eine konkrete Implementierung, mehrere in der Regel, mit
dem Zustand und einer Funktion, die heißt führe aus.
Und der führt eine Aktion aus, die normalerweise reicht dieser Befehl
alleine nicht an Daten, sondern ich muss mich auf irgendeinen
Empfänger beziehen und zum vorigen Beispiel war das das Dokument
selbst und die Anwendung.
Also gibt es einen Empfänger dieses Kommandos und dann führt die
Ausführerfunktion, führt dann eine Aktion aus, die der Empfänger zur
Verfügung stellt.
So sieht das in etwa aus.
So, was brauchen wir noch?
Beispiel haben wir, also hier ist ein etwas komplexerer Befehl, also
der öffnen Befehl könnte zum Beispiel so aussehen, dass die Funktion
führe aus, dann sagt, ich muss ja erst wissen, welches Dokument du
haben willst, ich rufe also eine Funktion aus, die den Benutzer fragt,
ein Auswahlfeld gibt, wo ich durch die Dateienhierarchie wandern kann,
dann erzeuge ich ein neues Dokument mit dem Namen, den ich jetzt
gekriegt habe, führe die Anwendung, füge hinzu aus, das ist das hier
und schließlich öffne ich noch das Dokument selbst mit der Funktion
öffnen.
Da ist also ein bisschen umfangreicherer Befehl.
Aber das werden Sie dann, wenn wir zur Implementierung kommen, mit
grafischen Oberflächen sehr häufig sehen, dass Sie ein Kommando bauen
müssen als eine Klasse und die dann irgendwo einfügen als eine
Referenz.
Und kriegen dann die Garantie von der Implementierung der grafischen
Benutzeroberfläche, dass wenn Sie auf dieses Menü klicken, dann wird
dieser Befehl mit dem entsprechenden Kommando ausgeführt.
Noch eine Variation daran, jetzt kann es sein, dass Sie mehrere
Befehle zusammenfassen wollen in einen gewissen Makrobefehl.
Das können Sie machen, das heißt, Sie machen jetzt wieder ein Befehl
mit der Aufrufschnittstelle Führe aus, dann gibt es ein Makrobefehl,
da ist eine Sammlung davon, das ist also ein Kompositum.
Sehen Sie es?
Ach ja, jetzt müssen Sie es langsam sehen, das ist das Kompositum, ja
klar, klar, klar, das ist das, was er macht, Führe aus, da ist
sicherlich ein Führe-Alle drin, ja in der Tat, wenn das ein
Makrobefehl ist, der drei Befehle drin hat, dann habe ich natürlich
hier drei Befehle dranhängen, ist ja dann eine Assoziation und da
führe ich für alle Dinge die Funktion Führe aus durch.
Fertig ist der Lack.
Ich kann also dann auch noch hergehen und meine Befehle
zusammenkapseln, in größere Befehlsumfänge zusammenfassen.
Auch das führt man manchmal aus.
Wo nehme ich jetzt das her?
Also ein Beispiel haben wir ja gesehen, wenn Sie Objekte haben, die
Sie mit einer auszuführenden Operation parametrisieren wollen, wie bei
den Menü-Eintragsobjekten.
Wenn Anfragen zu unterschiedlichen Zeiten spezifiziert aufgereiht und
ausgeführt werden sollen.
Das ist der Beispiel von grafischer Benutzeroberfläche.
Unterschiedliche Zeit sagen Sie, das ist das, was ich haben will.
Ich reihe sie auf, hänge sie irgendwo dran, in Erwartung, dass sie zu
einem späteren Zeitpunkt gewissermaßen unerwarteter Weise ausgeführt
werden, zu einem unerwarteten Zeitpunkt.
Wir können die Aktion des Benutzers nicht vorher sagen.
Ich kann es auch zum Rückgängig machen von Operationen machen.
Was mache ich da?
Wir sprachen vorhin von dem Memento.
Was ich jetzt machen könnte ist, dass ich zusätzliche Logik in das
Kommando einbaue, so dass es sagt, bevor ich mich ausführe, ich weiß,
was ich alles anfassen muss und ändern muss, mache ich mir Mementos
von dem ganzen Zeug, sichere mir die Mementos und führe dann den
Befehl erst aus.
Und diese Mementos, die reiche ich aus in einer langen Warteschlange
und wenn dann jemand kommt und sagt, rückgängig machen, dann weiß ich,
was zu tun ist.
Ich nehme mir die letzten Mementos her und restauriere den Zustand und
bin wieder in dem alten Bereich.
Sie wissen, wie praktisch das ist, wenn man mal was falsch macht.
Und nachdem wir schon undo können, können wir dann natürlich auch das
redo machen.
Wir haben eine lange Schlange von Operationen und wenn ich jetzt
erstmal ein undo gemacht habe, das war jetzt auch verkehrt, dann mach
ich es nochmal, ist ja alles vorhanden, wenn ich bei dem undo mir
wieder die geänderten Objekte gespeichert habe.
Das reicht also nicht, in der Schlange einfach nur hin und her zu
fahren.
Ich muss auch für das redo ein bisschen was merken, aber das Kommando,
im Kommando kann ich diese Logik einbauen.
Ich kann das jetzt mit einem Memento machen, ich kann es aber noch
eleganter machen, indem ich das Memento gleich umwandle in wieder
einen Befehl, dessen Inhalt darin besteht, die Daten zu retten und
Entschuldigung, die Daten wieder herzustellen.
Dann muss ich nur noch diese Liste von Befehlen abarbeiten und da weiß
ich schon, wie es geht, nämlich per Ausführe.
Das macht es noch ein bisschen eleganter.
Genau, mitprotokollieren von Änderungen kann ich hier auch machen oder
eben, wenn ich Makrobefehle brauche, das kommt in manchen Anwendungen
durchaus vor, dass Sie mehrere Befehle hintereinander ausführen
müssen, die es auch in Einzelschritten gibt.
Denken Sie mal an Ihren beliebtesten Mail-Klienten, der eine
Riesenlatte von Befehlen zur Verfügung hat.
Manche davon könnten Sie auch per Hand aus einzelnen Befehlen
ausführen, aber da gibt es natürlich Bequemlichkeitsfunktionen, da
drücken Sie nur auf den Knopf und dann wird was weiß ich an alle in
einer Mailingliste geschickt.
Okay.
Gibt es Fragen zum Befehl?
Ich sehe keine.
Ich schaue mal wieder auf die Uhr.
Aha, wir haben noch sechs Minuten.
Wie soll ich sagen, also die schwierigen Muster haben wir eigentlich
hinter uns.
Auftraggeber, Auftragnehmer ist jetzt die politisch korrekte Form.
Es hat mal Master Slave geheißen, aber das darf man nicht mehr sagen.
Vielleicht können wir das bei Trump wieder sagen, keine Ahnung.
Jedenfalls die Situation, die Sie haben, ist, Sie haben einen
Auftraggeber, den Master und Arbeiterprozesse, die übrigens echt
parallel laufen sollen und der Master schickt Aufgaben an die
Arbeitgeber, Arbeitnehmer und die führen die aus und sagen, ich bin
jetzt fertig, hier ist das Ergebnis und der Master kann also ganze
Menge von Arbeiterfäden so bedienen und wenn wir also unterschiedlich
große Aufgaben haben, dann ist es natürlich so, dass manche der
Arbeiterfäden oder Worker etwas länger brauchen als andere, aber das
macht nichts, das gleicht sich irgendwie am Schluss aus.
Also nehmen wir zum Beispiel, wir wollen ein 3D Bild berechnen,
möglichst schnell, ein einzelner Prozessor ist dafür zu langsam, also
gehen Sie her und schneiden dieses Bild auf in einzelne Kacheln und
schicken die Erzeugung dieser Kacheln an die einzelnen Arbeiterfäden.
Wenn Sie jetzt vier Stück haben oder fünf Stück, dann geben Sie also
die erste Leiste ab.
Wenn der erste zurückkommt, sagen Sie, okay, das ist noch nicht
gemacht, kommt der nächste zurück, geben Sie dem das hier und so
weiter.
Je nachdem, wann die einzelnen Prozessoren fertig werden, geben Sie
ihnen mehr Arbeit und am Schluss gibt es eben keine Arbeit mehr und
dann sagen Sie, jetzt sind wir fertig.
Dann schickt man denen ein Signal.
Sie können Ihren Rechner auch als Arbeiter anmelden bei City at Home
oder bei Folding at Home, bei City at Home, da können Sie mithelfen,
nach extraterrestrialer Intelligenz zu suchen.
Und bei Folding at Home können Sie Ihren Rechner anmelden an einen
Master und sagen, ich helfe mit, wenn ich gerade nicht arbeite, um die
3D -Struktur von Proteinen zu bestimmen.
Da können Sie mitrechnen, wie die einzelnen Moleküle sich
gegeneinander anordnen, das ist das Folding at Home.
Ein typischer Fall von Master Worker, diesmal sogar über das Internet,
aber wir können das mit unseren Mehrkernrechnern natürlich auch am
einzelnen Rechner haben.
Da sind ja genug Prozessoren drin inzwischen.
Also, wenn es mehrere Aufgaben gibt, die unabhängig voneinander
berechnet werden können, wenn sie abhängig sind voneinander, dann ist
es blöd, weil dann müssen die Arbeiter gegenseitig aufeinander warten
und das verzögert alles.
Wenn Sie mehrere Prozessoren zur gleichzeitigen Verarbeitung haben,
entweder als Ihren eigenen Mehrkernrechner oder über ein Netz und wenn
Sie die Belastung der einzelnen Arbeiter über die Zeit ausgleichen
wollen, das heißt, derjenige, der ein Arbeitspaket gibt, das lange
dauert, rechnet natürlich auch länger, während die anderen flott
fertig werden, die kriegen dann aber zusätzlich Arbeitspakete.
Da gibt es noch Varianten davon, das können Sie dann in
Parallelverarbeitungs Vorlesungen hören, das heißt Work Stealing, also
das Klauen von Arbeit, da wird die Arbeit erst mal verteilt an
verschiedene Arbeiter, mehrere Arbeitspakete pro Arbeiter.
Wenn aber einer ganz schnell fertig ist mit seinen, darf er sich bei
anderen Arbeit holen, also Arbeitspakete aus seinem Bereich klauen,
deswegen Work Stealing.
Das ist aber jetzt nur nebenher zu einem Ausblick.
So.
Dann haben wir schon fertig mit dem Master Worker, Auftraggeber,
Auftragnehmer, wir hatten die Befehle gehabt und das war der Kommando,
Steuerungsmuster, gibt natürlich mehr.
Jetzt kommen die Bequemlichkeitsmuster.
Die Bequemlichkeitsmuster, die kann man ganz schnell machen, die sind
nämlich sofort fertig.
Bequemlichkeitsmethode, Bequemlichkeitsklasse ist so, angenommen, Sie
haben eine Anfrage, die braucht immer drei Parameter oder 27
Parameter, zum Beispiel, stellen Sie sich vor, Sie möchten eine
Funktion plotten, also Funktionsablaute brauchen Sie, x-Achse, y
-Achse, Aufteilung, Skalen, Beschriftungen, Funktionen und so weiter.
Aber es gibt auch Fälle, wo die Werte ein Standard sind.
Dann merken Sie sich den Standard und schreiben eine
Bequemlichkeitsfunktion, die hat gar keine Parameter, die ruft nämlich
die große Anfrage auf, aber mit den Standardparametern.
Einfach nur eine Abkürzung für die Schreibweise, kennen Sie natürlich
schon als Java-Programmierer, dass Sie manchmal eine Methode haben
durch Überladen, die viele Parameter hat und dann die gleiche mit
weniger Parameter, da müssen Sie den Rest der Parameter irgendwo
herhaben.
Das können Sie zum Beispiel durch das Ersetzen des Zustandes
erreichen, des Standardzustandes oder die Convenience-Methode ist
genau das gleiche.
Da gibt es halt feste Werte, da sagen Sie, das ist die lange Anfrage,
die kurze Anfrage mit P1 nur, dann habe ich halt Standardwerte, die
ich hinzufüge.
Das ist alles.
Die Bequemlichkeitsmethode durch Überladen und Weglassen mancher
Parameter und sie durch Standardwerte zu ersetzen.
Jetzt sind wir genau fertig.
Was sagen Sie zu mir?
Vielen Dank.