Wie immer fangen wir an mit ein bisschen Quatsch.
Und zwar heute mit dem Automatisierungsparadoxon.
Das heißt, es wird ein bisschen philosophisch heute.
Es geht nicht nur darum, was es denn für Fehler geben kann in
Softwaresystemen, sondern auch, was unsere Rolle in Softwaresystemen
eigentlich ist.
Und damit meine ich jetzt ausnahmsweise mal nicht unsere Rolle als
Entwickler, sondern auch als Nutzer, denn wir alle nutzen ja auch
Softwaresysteme zur Genüge.
Und inzwischen tut das ja eigentlich jeder.
Unser Beispiel ist zugegeben ein paar Jahre älter, aber ich denke,
dass das ein relevantes Thema ist.
Stichwort Digitalisierung sollte klar sein.
Das ist also eher etwas, was in den letzten Jahren noch schlimmer
geworden ist als besser.
So, worum geht's?
Es geht um den Air Malaysia Flug 124.
Wir haben uns ja extra ein Beispiel rausgesucht, wo nichts passiert
ist.
Das schon mal vorneweg.
Wenn ich das jetzt hier jemand denkt, Flugzeug abgestürzt, hunderte
Tote, sowas stellen wir in der Vorlesung vor.
Nein, machen wir natürlich nicht.
Also da ist nichts passiert.
Es geht aber darum, was passiert, wenn Softwaresysteme ausfallen und
wie der Mensch dann darauf reagiert oder reagieren sollte.
Also, Air Malaysia Flug 124.
Wollen Sie zur Softwaretechnik, dann sind Sie richtig.
Ja, ist eine Übung.
War trotzdem schön mit Ihnen.
Jetzt war er doch schon aufgestanden, ich verstehe es nicht.
Darf ich das als persönliche Kritik annehmen?
Okay, ich mache trotzdem mal unbeirrt weiter.
So wie man das halt macht, wenn man vorne steht.
Wo war ich?
Es geht um den Air Malaysia Flug 124.
Das war ein ganz normaler Flug von Perth in Australien nach Kuala
Lumpur.
Geplante Flugdauer 5 Stunden, nichts wildes also.
Eine ziemlich moderne Boeing zu dem Zeitpunkt, eine 777-200.
Und nach 18 Minuten, ja, da waren wir dann bei 36.000 Fuß nach dem
Start.
Ja, mit Autopilot, alles wunderbar, so wie man das heute macht.
Fliegt ja keiner mehr, macht ja die Maschine alles alleine.
Start, Flughöhe erreichen und dann ist ja alles gut.
Oder auch nicht.
Was dann passiert ist, Flughöhe erreicht, plötzlich zieht das Flugzeug
steil nach oben.
Und zwar ca.
18 Grad und das merkt man in so einem Flugzeug.
Das fühlt sich nicht besonders schön an und außerdem ist man ja schon
auf Flughöhe und so besonders viel weiter hoch sollte man jetzt mit so
einem Flugzeug auch nicht unbedingt.
Das heißt, das wurde dann schon recht unangenehm.
Irgendwann war man dann bei 39.000 Fuß und dann gingen Alarmleuchten
an.
Und zwar witzigerweise die Alarmleuchte für Strömungsabriss und die
für Übergeschwindigkeit.
Das sind ja Dinge, die können nicht passieren.
Also Strömungsabriss hat man ja nur, wenn man zu langsam wird.
Dass man irgendwann nicht mehr genug Strömung unter den Flügeln hat
und dann halt keinen Auftrieb mehr hat.
Und gleichzeitig Übergeschwindigkeit, das widerspricht sich irgendwie.
Das heißt, man hat hier eigentlich eine unmögliche Kombination von
Warnungen und naja, logischerweise sind dann die Piloten darauf nicht
vorbereitet.
Also kein Training für die Piloten für solche Fälle, weil das kann ja
nicht passieren, dass die zwei Sachen gleichzeitig passieren.
Glück gehabt, die Piloten waren ziemlich gut und haben das dann
trotzdem unter Kontrolle bekommen.
Haben das Flugzeug wieder nach unten gebracht auf die stabilen 36.000
Fuß.
Und dann war es dort und dann ist das Gleiche nochmal passiert.
Da ist das Flugzeug wieder steil nach oben gegangen im Autopilot und
man hatte im Grunde die gleiche Situation.
Aber die Piloten haben wieder richtig reagiert und haben dann gesagt,
so jetzt reicht es.
Mit dem Ding fliegen wir jetzt manuell ohne Autopilot zurück zum
Flughafen.
Und dort hat man sich das Ganze dann mal angeschaut, mal geschaut, was
ist denn da eigentlich schief gelaufen.
Und dann hat man gesehen, dass in dieser Air Data Interior Reference
Unit zwei Beschleunigungssensoren ausgefallen waren, die genau die
Fluglage überwachen sollen.
Und die erste war schon seit vier Jahren defekt und die zweite ist
ausgefallen, kurz nachdem das Flugzeug auf über 36.000 Fuß gestiegen
war.
Und man hat Redundanz, also eigentlich kein Problem.
Macht man ja sehr gerne, gerade in der Luftfahrt, dass man sagt, wenn
ein System ausfällt, haben wir einfach noch fünf andere.
Und das war auch in dem Fall so.
Also sechs Beschleunigungssensoren, zwei davon sind ausgefallen, vier
gehen noch.
Was soll da eigentlich schief laufen?
Eigentlich ist doch alles wunderbar.
Das Problem war, durch diese redundante Auslegung hat man halt auch
nie geprüft, ob da irgendwas kaputt ist.
Das war einfach nicht vorgesehen.
Laut Protokoll braucht man ja nicht machen, wenn es eines ausfällt,
fliegt man halt weiter, weil es sind ja noch fünf da.
Das Problem ist, dass da jemand einen Softwarefehler eingebaut hat.
Und dieser Softwarefehler sah im Wesentlichen so aus, dass wenn ein
Sensor ausfällt, dann schau halt auf die anderen.
Es funktioniert wunderbar, wenn der erste Sensor ausfällt, dann nimmt
man halt die anderen fünf.
Wenn der zweite Sensor ausfällt, kann man sich überlegen, was
passiert.
Dann ist plötzlich der erste Sensor, der eigentlich kaputt ist, wieder
im Spiel, laut Software.
Und dadurch hat dann im Grunde dieses Fehlverhalten im System dazu
geführt, dass die Werte des ersten Sensors, der eigentlich ausgefallen
war, wieder mit rein kamen.
Und dann das Flugzeug halt meinte, okay, ich muss nach oben ziehen.
Offensichtlich hat man es nicht gefunden, weil das Team, das diese
Unit da entwickelt hat, da nicht dran gedacht hat.
Wie auch immer man nicht dran denken kann, dass zwei Sensoren
ausfallen können, wenn der erste ausfallen kann, sollte man also
machen.
Was man jetzt hier schön sieht, ist folgendes.
Und zwar ein Phänomen, was schon länger bekannt ist.
Und zwar schon in den 80er Jahren formuliert, nämlich das
Automatisierungsparadoxon.
Und zwar ist es so, dass der Beitrag des Menschen zum Erfolg des
Systems immer wichtiger wird, je fortgeschrittener das System ist.
Auf der anderen Seite hat man selber immer weniger Einfluss aufgrund
der Automatisierung.
Klassisches Beispiel, Sie fahren mit Ihrem Tesla im Autopilotenmodus
mit 200 über die Autobahn und dann kommt plötzlich ein LKW, der
kreuzt.
Den Fall hat es ja tatsächlich gegeben.
Dann haben Sie besonders wenig dazu beigetragen, in diese Situation zu
kommen.
Sie haben ja selber wahrscheinlich nicht auf die Geschwindigkeit
beschleunigt.
Sie haben selber nicht dorthin gelenkt.
Aber in dem Moment müssen Sie dann besonders gut reagieren, weil dann
müssen Sie entweder voll auf die Eisen gehen oder irgendwie
ausweichen, was auch immer.
Das heißt also, der Beitrag des Menschen wird immer größer, seine
Einflussmöglichkeiten werden aber immer kleiner.
Und das ist im Grunde genau das, was hier steht.
Jetzt können wir nochmal schauen, was passiert, wenn wir diesen
Einfluss einfach mal weglassen.
Jetzt das Gegenbeispiel.
Was passiert, wenn der Mensch nicht aufpasst?
Hier geht es um das schöne Kreuzfahrtschiff, die Royal Nasty, die auf
Grund gelaufen ist.
Und man fragt sich auch da wieder so ein bisschen, wie kann sowas denn
eigentlich passieren?
Denn normalerweise auf so einem Kreuzfahrtschiff hat man ein super
modernes Cockpit, man hat GPS-Navigation, elektronische Seekarten, ein
eingespieltes Team, man hat vorgebene Wachprozeduren, festgelegte
Protokollen usw.
Also was soll denn da eigentlich schief gehen?
Zum Beispiel das GPS-Gerät kann ausfallen.
Und zwar schon nach 52 Minuten nach dem Start wird von der
eigentlichen Antenne getrennt, die das GPS-Signal entgegennehmen soll.
Das heißt, es gibt einfach keine GPS-Daten mehr.
Was passiert?
Eigentlich soll mit dem GPS-System natürlich der Standort bestimmt
werden, alles gut.
Diese Schätzung des Standorts wird an den Kartenplotter weitergegeben
und auch an den Autopiloten.
Und auch hier war es natürlich so, das Schiff fährt schön auf
Autopilot, weil das funktioniert ja wunderbar.
Mit dem GPS gibt man einmal vorher noch die Route ein und dann fährt
das Ding einfach.
Dass da was passiert ist, was nicht mehr funktioniert, das wurde schon
angezeigt.
Da war so eine rote Blinkeleucht, die hat gesagt GPS kaputt.
Nur hat da 34 Stunden keiner mehr drauf geschaut.
Das heißt, da hat sich jemand ein bisschen zu sehr auf den Autopiloten
verlassen.
Obwohl, ich glaube so war es, eigentlich eine stündliche Prüfung
vorgesehen war.
Und was ist jetzt die Lehre davon?
Also egal wie sehr wir uns bemühen, wir werden immer in die Situation
kommen, dass wir an irgendwas nicht gedacht haben.
Da werden wir einfach nie dran vorbeikommen.
Egal ob wir uns alle Zustände für unser System überlegt haben, ob wir
den Zustandsautomaten noch so sorgfältig designt haben.
Irgendwas haben wir garantiert vergessen.
Das Problem dabei ist, die Zuverlässigkeit des Systems wird immer
schwieriger sicherzustellen, je näher wir an die 100% kommen.
So bis 80% ist das ziemlich einfach.
Da kann man wunderbar mit Tests zum Beispiel abprüfen, ob das alles so
tut, wie es soll.
Aber je näher man an die 100% kommt, desto schwieriger wird es, desto
aufwendiger wird es herauszufinden, wo noch potenzielle Fehler liegen
könnten.
Und wenn wir das als Benutzer eben nicht erkennen, dann können die
Folgen dramatisch sein, wie wir gesehen haben.
So ein Schiff läuft auf Grund, auch da ist zum Glück nichts passiert.
Bei einem Flugzeug ist nichts passiert, aber es ist glaube ich
offensichtlich, dass schnell hätte was passieren können.
Und die Metalehre daraus ist noch, man sollte versuchen, so nah wie
möglich dran zu kommen.
Aber man sollte immer damit rechnen, dass man die 100% nie erreichen
kann.
Bei Prüfen, beim Testen, wie auch immer.
Und das wird uns auch nochmal in der Vorlesung beschäftigen, wenn es
dann um Testen geht.
Da werden wir auch nochmal darüber sprechen, wann man denn eigentlich
fertig ist mit Testen zum Beispiel.
Das ist immer wieder ein Thema.
Behalten Sie das dann da bitte mit im Hinterkopf.
Gut, das war's dazu.
Jetzt macht Tobias weiter mit der eigentlichen Übung.
Gott sei Dank haben wir kein Automatisierungssystem geschrieben,
zumindest bisher noch nicht.
Wäre aber mal schön, oder?
Wäre mal schön, oder?
Einfach so aus den Folien direkt so Sprachgenerierung machen.
Achso, damit wir den Feiertag auch richtig nutzen können, meinst du?
Genau.
Also, oder nicht in die Übung gehen.
Genau, also ich fände das gut.
Aber es ist die Frage, ob dann einer von uns noch hinten sitzen
müsste, um zu überprüfen, ob irgendwas schiefläuft, um dann
einzugreifen.
Für die wirklich schwierigen Fälle.
Für die wirklich schwierigen Fälle, wenn zum Beispiel mal jemand eine
Frage stellt, ganz unerwarteterweise.
Ja, weil die Chatbots noch nicht gut genug sind.
Genau.
Gut.
Machen wir weiter mit der Übung.
Was war unsere erste Aufgabe?
Wir hatten die grafische Benutzeroberfläche für iMatch, unseren
iCatcher.
Fangen wir vielleicht erstmal mit dieser wunderhübschen Zeichnung an.
Sebastian hat ziemlich viel Zeit da reingesteckt.
Alles nur für Sie.
Wer hat es denn gemacht?
Für wen hat es sich denn gelohnt, dass ich das so schön gemalt habe?
Das sind zwei Fragen.
Du musst die trennen.
Ja, okay.
Also, wer hat es gemacht, das war natürlich die Frage.
Ich habe das schon so interpretiert, alle, die sich gerade eben
gemeldet haben.
Wer fand die Zeichnung schön?
Ey, das hat mich fast eine halbe Stunde gekostet.
Das glaube ich dir gerne, wenn die halbe Stunde gereicht hat.
Können wir drüber nachdenken noch viel länger.
Genau, also unsere wunderhübsche Zeichnung am Whiteboard.
Fangen wir vielleicht erstmal mit dem einfachen Teil an.
Ich werde jetzt nicht ins Detail gehen und nicht jeden einzelnen Punkt
der Implementierung dieser Benutzeroberfläche hier heute vorstellen,
sonst wären wir hier wahrscheinlich zwei Stunden beschäftigt damit.
Ist vielleicht nicht ganz so spannend, aber so ein paar exemplarische
Ausschnitte unserer Implementierung möchte ich dann doch gerne zeigen.
Vielleicht erstmal zunächst mit dem einfachsten Element in der ganzen
Geschichte, nämlich diesem Popup hier unten rechts für das
Ergebnisbild, auf das man das erzeugt werden sollte.
Wenn man hier oben drauf klickt, das ist nichts anderes als ein J
-Dialog mit einem J-Scroll-Pane hier unten und an der Seite.
Man sollte eine feste Größe für dieses Fenster wählen.
Wer das natürlich jetzt auch noch schön dynamisch an die
Bildschirmgröße angepasst hat, ist natürlich noch hübscher, war aber
nicht gefordert.
Sie hätten also einfach nur eine feste Größe wählen müssen und alles
andere mit den Scroll-Balken dann regeln sollen.
Soviel vielleicht erstmal zu diesem sehr einfachen Moment, aber jetzt
schauen wir uns mal diese hübsche Zeichnung auch noch in Real an.
So sieht unsere Musterlösung aus.
Ich finde sie ist relativ nah dran.
Wobei deine Zeichnung hübscher ist.
Aber das liegt vielleicht daran, dass diese Standard-Java-Gui-Gesachen
nicht so richtig hübsch sind.
Was haben wir jetzt hier eigentlich?
Wir haben mehrere Elemente, die mittels eines Kompositum
zusammengeschalten sind, wie das bei grafischen Benutzeroberflächen
immer der Fall ist.
Wir haben hier zwei J-Panel, die übereinander liegen, ein oberes und
ein unteres.
Das obere ist allerdings wieder unterteilt in zwei weitere, die da
drüber liegen, wo dann innen drin sogar noch weitere Unterteilungen
sind.
Soviel zum Thema Kompositum.
Man kann einzelne Elemente in Swing immer wieder ineinander
schachteln.
Die ganzen Gui-Elemente lassen sich immer ineinander stecken und somit
das Kompositum zusammenbauen.
Fangen wir vielleicht erstmal mit dem oberen der beiden ersten Panel
an.
Das ist jetzt in unserem Fall das Upper-Panel, welches alles hier oben
hält und auch bereits einen Observer, also einen Besucher,
implementiert.
Das ist ein kleiner Hinweis darauf später.
Ich zeige nachher noch, wie das funktioniert.
Aber unsere Architektur benutzt in diesem Fall Modell, Präsentation
und Steuerung.
Das heißt, wir haben alle Gui-Elemente in der Präsentation, wir haben
ein Modell, wo die Daten gehalten werden und wir haben eine
Steuerungseinheit, die zwischen diesen beiden vermittelt.
Und das Registrieren dieses Besuchers hier läuft über die Steuerung,
die dann dem richtigen Teil des Modells den richtigen Händler zuweist,
der dann weiß, was er damit tun muss.
Gut, aber erstmal ein bisschen zum Layouting in Swing.
Kurze Frage, wer hatte Spaß mit Layouts in Swing?
Ich hab's befürchtet.
Waren das jetzt schon die ironischen Melder oder waren das die, die
ernsthaft Spaß hatten?
Machen wir es mal andersrum, wer hatte denn keinen Spaß?
Wer findet es gruselig?
Okay, sind wir uns wenigstens alle einig.
Es ist ein bisschen gruselig, aber wir haben nicht so viele
Möglichkeiten, irgendwas anderes im Rahmen dieser Vorlesung mal
auszuprobieren.
Es ist auch ganz nett, das Ganze mal tatsächlich von Hand gemacht zu
haben.
Später kann man natürlich sonstige Werkzeuge benutzen, die einem da
ganz viel schon vorliefern.
Unser Ziel war es, dass man auch einmal gesehen hat, wie man das von
Hand machen muss.
Es ist ein bisschen schmerzhaft, wir wissen das, aber gucken wir uns
das mal noch genauer an.
Was wir jetzt hier verwendet haben, ist ein Gridback-Layout, welches
gewisse Abstände definiert und als Layout für dieses obere Panel
gesetzt wurde, in dem dann das linke und das rechte Panel, was wir
eben schon gesehen haben, zugefügt werden.
Fangen wir mal mit dem linken Panel an, weil das relativ
straightforward ist.
Da haben wir jetzt einmal das Image-Panel, was das Bild hält und die
Scroll -Pane unten drunter, die den Slider für das Bewegen des Bildes
zur Verfügung stellt.
Das ist eigentlich eine feste Größe für das Image-Panel.
Und es gibt eine feste Größe für den Scroll-Balken, der natürlich
genauso breit sein sollte wie...
Warte mal, der Scroll-Balken hat keine Größe.
Das Left-Panel hat...
Ah doch, das ist der Scroll-Balken.
Der hat natürlich die Größe, die dem Bild entspricht und
dementsprechend auch damit arbeiten kann.
Wie kommt jetzt das Bild da rein an der Stelle überhaupt?
Dafür haben wir hier die Methode setInput, welche dann je nachdem von
dem Händler aufgerufen wird.
Aber das sehen wir später noch, wie das funktioniert.
Was dann passiert, es wird das Model übergeben.
Das heißt, wir haben das Bild im Model gespeichert und lesen aus
diesem Model jetzt die Bilder aus.
Es sind ja mehrere in dem Fall.
Schauen dann erstmal, ob das überhaupt vorhanden ist.
Ob es momentan schon das gleiche ist, wie das, was wir schon gesetzt
haben.
Da müssen wir nichts mehr tun.
Wenn es leer ist, dann können wir auch nichts machen an der Stelle.
Dann machen wir ein leeres Bild hier oben rein.
Und ansonsten bauen wir uns dieses zusammengesetzte Bild aus diesen
mehreren Bildern, die wir in den Inputs haben, zusammen.
Das passiert dann hier in diesem Stück hier.
Wir machen ein neues Graphics2D Object, auf dem wir dann die
verschiedenen Bilder nebeneinander malen.
Und dann hier unten noch einmal das Padding hinzufügen, dass wir auf
die Größe kommen, wie die Dimension unseres Image Panels ist.
Das ist hier ausgelagert in dieser Hilfsmethode getImage.
Können Sie sich in der Musterlösung anschauen, wenn Sie das
interessiert, wie das genau funktioniert.
Aber erstmal, dass man grob eine Idee bekommt, wie das Ganze
funktionieren kann.
Und dann wird natürlich dieses Bild dann auch gesetzt und das Image
Panel dualisiert.
Gibt es Fragen zu diesem Image Panel bisher?
Gut, dann mache ich mal weiter.
Jetzt gucken wir uns mal die rechte Seite an.
Wir haben natürlich auf der rechten Seite auch noch oben ein Image
Panel.
Aber das ist dann wieder genau das gleiche, wie wir es eben schon
gesehen haben.
Jetzt schauen wir uns mal noch kurz die Knöpfe an, die wir hier unten
haben.
Das ist eigentlich auch ziemlich straightforward.
Wir haben einfach drei Knöpfe, die wir anlegen und an die richtigen
Stellen in dem Gridback-Layout legen.
Und dann haben wir die da.
Keine große Magie hinter dieser Geschichte.
Jetzt kommen wir mal noch zum unteren Panel an der Stelle.
Auch hier haben wir wieder Knöpfe, die wir hinzufügen müssen.
Wieder diese drei hier unten.
Wir haben wieder den Load-Folder, wir haben den Load-Curve und wir
haben den Run-HDR-Button, die an die richtigen Stellen gesetzt werden
müssen.
Wir haben auch hier wieder ein Gridback-Layout, was wir benutzt haben.
Von daher alles genauso, wie wir es eben auch schon gesehen haben.
Jetzt wird es ein bisschen spannender.
Jetzt kommen wir mal noch zu den anderen Elementen, weil Knöpfe
anschauen ist irgendwie erstmal nicht so ganz so spannend.
Jetzt gucken wir uns erstmal eigentlich genau diesen Teil der
Benutzeroberfläche an.
Natürlich haben wir jetzt hier im oberen Teil erstmal die Label.
Also erstmal das Label für Samples an der Stelle.
Dann gibt es noch ein Label für die Camera-Curve und es gibt noch ein
Label für das Lambda.
Und dann haben wir hier unten die erste Combo-Box.
Wir sollten ja zwei Combo-Boxen dort benutzen.
Das ist jetzt die für die Camera-Kurve.
Wir haben hier einen Enum, was benutzt wird, über das diese Elemente,
die dort eingetragen werden können, ausgelesen werden.
Also wir haben einmal Calculated Curve, einmal Loaded Curve und wir
haben noch Standard Curve, glaube ich.
Das sind die drei Punkte, die in diesem Enum-Curve-Mode stehen.
Und die werden dann dort als Labels hinzugefügt.
Dann haben wir noch den wunderschönen Slider für die Samples.
Das ist so ein J-Slider, den wir hier benutzen.
Hier sieht man jetzt, dass auch schon ein erster Change Listener
benutzt wird.
Also auch hier haben wir schon eine erste Implementierung von einem
Beobachter, der an der Stelle schaut, ob sich das geändert hat und je
nachdem hier oben dieses Label aktualisiert.
Da musst du jetzt in dem Fall nicht über das Modell oder den
Controller gehen, weil das eigentlich nur eine Geschichte ist, die
zwischen den beiden Elementen der Präsentation konsistent gehalten
werden muss.
Die anderen Geschichten laufen allerdings alle über den Controller,
also die Steuerungseinheit.
Was man jetzt hier noch sieht, wir haben ein Minimum für den Slider
und ein Maximum, 1 bis 1000.
Haben dann noch angegeben, wie jetzt die einzelnen Ticks aussehen
sollen dafür und ob sie gezeichnet werden sollen.
Das ist jetzt so ein krummer Wert, weil das so ein bisschen schwierig
ist mit 1 bis 1000.
Wenn man das mit 0 bis 1000 macht, dann hat man da einen geraden Wert.
Ansonsten ist das Ganze auch wieder in den Gridback-Layout eingefügt,
dass die Dachen hier nebeneinander liegen.
Das heißt, wir haben eigentlich so eine Art-Matrix mit einer
Gewichtung drin, die dafür sorgt, dass wir hier die einzelnen Elemente
schön nebeneinander liegen haben und auf derselben Höhe.
Gibt es hierzu Fragen?
Oder Fragen bisher?
Gut, dann mache ich weiter.
Dann kommen wir jetzt nämlich zu dem Lambda-Feld und auch jetzt mal so
ein bisschen tiefer gehen, wie das Ganze mit der Steuerung dahinter
funktioniert.
Also wie das in unserer Implementierung funktioniert, ist erstmal, das
ist jetzt das GUI-Element, das ist einfach ein Textfeld, was 10
Zeichen hat.
Und das ist jetzt erstmal nicht so spannend, aber schauen wir uns doch
mal das Modell dahinter an.
Also in unserem Modell gibt es den Wert Lambda, der gespeichert wird.
Und es gibt auch die Methode setLambda, ein Setter dafür.
Und dieser Setter bekommt immer einen Float-Wert Lambda übergeben und
überprüft dann, ob das halt derselbe Wert ist, den wir schon momentan
haben.
Dann ist alles okay, dann ist alles in Ordnung.
Dann überprüft er noch, ob das Lambda valide ist, was dort übergeben
wurde.
Das ist das, was wir auch schon in der Kommando-Zeilen-Geschichte
gemacht haben.
In dem Fall würde es Falls zurückgeben, wir sehen gleich noch, wo
dieser Bullshit-Wert dann tatsächlich nachher benutzt wird.
Und wir haben hier noch den Fall, was wäre denn jetzt, wenn wir eine
Calculated Curve ausgewählt haben.
Das war die Geschichte mit, eine Calculated Curve haben, dann müssen
wir nicht mehr das Lambda wählen können.
Stimmt das eigentlich?
Ja, an der Stelle, genau.
Und hier unten sieht man jetzt schon so ein bisschen, wie funktioniert
das mit dem Beobachter.
Denn wir kennen ja aus der Vorlesung, aus dem Entwurfsmuster, dass die
beobachtenden Objekte, die sie beobachtenden, benachrichtigen.
Das ist hier dieses Inform Observers, das heißt, alle angemeldeten
Beobachter an dem Modell werden betrachtet, dass sich hier was
geändert hat und sollen dann schauen, was sie damit tun.
Nochmal ganz kurz, bei dem Calculated Curve, da werden die
Ergebnisbilder auf Null gesetzt, weil du ja jetzt neu berechnest, wenn
du das Lambda geändert hast.
Richtig, genau.
Und jetzt schauen wir mal so ein bisschen in den Steuerungsteil rein.
Wir haben eine Steuerungsklasse, die das ganze Zuweisen macht.
Die bekommt einfach die Präsentation, das Präsentationselement und das
Modell und sagt dann, dieses Präsentationsmodell meldet sich mit
diesem Händler an diesem Teil des Modells an.
Das ist ein bisschen das Befehlsmuster dahinter.
Könnt ihr euch aber dann genauer anschauen, wenn ihr die Musterlösung
seht.
Jetzt mal hier exemplarisch einen dieser Händler, den Parameter Update
Händler, der natürlich auch ein Besucher ist und der hat jetzt diese
SetLambda -Methode.
Und diese SetLambda-Methode schaut er sich jetzt an, was ist das
momentane Lambda in der Präsentation?
Also was ist das Lambda, was hier gerade eingetragen ist?
Holt sich also die Zeichenkette, die hier in diesem Feld eingetragen
ist, versucht diese in einen Float zu parsen.
Wenn das fehlschlägt, setzt er den Float auf Null.
Und hier unten in diesem schönen kryptischen Aufschrieb betrachtet er
jetzt noch, ist das Lambda, was ich da bekommen habe, jetzt insgesamt
invalid.
Es ist invalid, wenn das L auf Null gesetzt wurde oder wenn ich auf
Model die SetLambda-Methode aufrufe und ich da jetzt des Falls
zurückkriege.
Dann ist das Ganze invalid und in dem Fall wird dann die SetLambda-to
-Invalid -Methode auf dem View aufgerufen, die dann dafür sorgt, dass
das Ganze auf Rot gesetzt wird.
So funktioniert jetzt das Zusammenspiel zwischen dem Modell, der
eigentlichen Präsentation und der Steuerung.
In dem Fall alles über Beobachter geregelt.
Das heißt, jedes Mal, wenn sich irgendwas ändert, kriegen die anderen
mit, was sich jetzt da geändert hat und warum.
Sie müssen natürlich nicht genau diese Architektur benutzt haben.
Sie mussten nicht für die Lösung jetzt unbedingt View-Controller
umgesetzt haben.
Wir haben das jetzt hier in unserer Lösung so gemacht.
Das ist die Standard-Vorgehensweise, wenn man tatsächlich irgendwelche
Benutzerschnittstellen baut.
Denn jetzt kann man dieses Modell jederzeit für andere Dinge benutzen,
genauso die Präsentation.
Man kann die Sachen austauschen und kann vor allen Dingen auch schön
gekapselt die einzelnen Funktionalitäten in einzelnen Bereichen haben.
Aber das haben wir ja alles in der Vorlesung auch schon gehört.
Das haben wir jetzt nicht gefordert, das ist jetzt unsere Lösung.
Das heißt, Ihre Lösung kann natürlich sehr stark von dieser Lösung
hier sich unterscheiden.
Uns war wichtig, dass Sie mal eine Benutzeroberfläche gebaut haben und
auch die Funktionalität in irgendeiner Form umgesetzt haben.
Wie Sie das jetzt genau gemacht haben, muss dann Ihr Tutor
entscheiden, ob das okay ist.
Aber Sie müssen jetzt nicht genau diese Lösung haben, wie wir sie
jetzt hier haben, nur dass das klar ist.
Also wir haben auch die Tutoren angewiesen, hauptsächlich die
Funktionalität der Benutzeroberfläche, so wie es auf dem Übungsblatt
beschrieben war, zu testen.
Es geht da weniger darum, ist es jetzt besonders schön mit besonders
vielen Entwurfsmustern und Model-View-Controller implementiert,
sondern funktioniert es so, wie es vorgegeben war.
Das heißt, eine deutlich einfachere Lösung, die nicht so gut
erweiterbar und austauschbar ist, hätte auch genügt in dem Fall.
Also nicht jetzt abschrecken lassen, weil wir das so super kompliziert
gebaut haben.
Wir wollten es bloß für die Übung besonders hübsch haben und werden
das natürlich auch zur Verfügung stellen, damit Sie sich das mal
anschauen können, wie man das hübsch bauen kann.
Sie sollten sich das, wenn Sie das anders gelöst haben, natürlich
sicherlich mal zu Hause anschauen, weil es ganz interessant ist, mal
zu sehen, wie sowas funktioniert.
Genau diese Probleme werden Sie immer laufen, wenn Sie auch
irgendwelche Android-Apps, irgendwas anderes programmieren.
Auch da wird jederzeit immer wieder Model-Präsentationen-Steuerung
benutzt werden in irgendeiner Abwandlung davon.
Das heißt, das einfach mal gesehen zu haben und sich anzuschauen, wie
das funktioniert, ist durchaus hilfreich für spätere mögliche
Anwendungen.
Jetzt noch eine kurze Frage, wer hat die Bonusaufgabe gemacht?
Die Bonusaufgabe an der Stelle war... also es gab eigentlich zwei
Bonusaufgaben, vielleicht hätte ich das präzisieren müssen.
Es gab eine Bonusaufgabe, die das Laden und Speichern der Kurve
hinzugefügt hat und dann jetzt eine Bonusaufgabe, die noch das
Anzeigen macht.
Das mit dem Laden und Speichern lasse ich jetzt mal weg.
Wir zeigen jetzt hier mal nur unsere einigermaßen hübsche Darstellung
der Kurve.
Die sieht nicht ganz so hübsch aus wie deine Zeichnung, aber das lag
daran, dass die Kurve halt nicht so hübsch aussah.
Blöd, ne?
Hätte man eine realistische Kurve zeichnen können damals.
Naja, wenn du weiter reinzoomen würdest, könntest du das schon auch so
machen.
Also wenn du die einzelnen Punkte weiter auseinandernehmen würdest,
würde das schon gehen.
Aber dann musst du halt wieder mit einem sehr großen Bild anfangen und
dann hast du wieder Scrollbalken.
Deshalb ist es so, glaube ich, auch ausreichend.
Okay, aber schauen wir uns jetzt mal noch genauer an, wie die Kurve
aussieht.
Irgendwie ist der Beamer auch nicht besser jedes Jahr, oder?
Nee, man sieht es auch leider gar nicht so schön, wie das eigentlich
aussieht.
Das heißt, dann zu Hause nochmal PDFs anschauen.
Oder tatsächlich die Musterlösung mal laufen lassen.
Hä?
Musterlösung laufen lassen ist auch eine Option.
Ja, aber das wird ja nicht besser, oder?
Davon wird der Beamer ja nicht besser.
Nein.
Achso, du meinst zu Hause mal die Musterlösung laufen lassen.
Sehr gute Idee.
Genau.
Also, was haben wir da jetzt gemacht?
Wir haben ein Koordinatensystem aufgebaut, in dem wir die einzelnen
Kurven mittels ganz vieler einzelner Punkte darstellen.
Das haben wir so gewählt, damit man auch Überlappungen besser erkennen
kann.
Wenn man einfach nur Linien zieht, kann es sein, dass die Kurven, wenn
sie komplett übereinander liegen, nicht auseinanderzuhalten sind.
Das heißt, wenn wir Kreise malen, dann haben wir mehrere Farben
übereinander und können das besser erkennen an der Stelle.
Wie haben wir das Ganze jetzt hier gemacht?
Wir haben uns erstmal berechnet, wie groß ist der Wertebereich.
Ja gut, von 0 bis 256 ist klar, also x-Achse ist klar an der Stelle,
weil wir die alle abdecken müssen.
Aber für die schöne Darstellung will man vielleicht nicht von 0 bis
1000 gehen.
Dann wäre das Ganze hier unten nur eine Linie, dann sieht man gar
nichts mehr.
Sondern wir berechnen uns hier den y-Wertebereich aus den maximalen
Werten der drei Kurven, also R, G und B.
Und schauen dann, dass wir da noch ein bisschen was drüber...
Wir haben jetzt noch 0,5 mehr an der Stelle, dass wir ein bisschen
Abstand nach oben auch noch haben.
Und wir nutzen das als y-Wertebereich an der Stelle.
Dann zeichnen wir noch Linien, damit man so ein bisschen erkennt, auf
welcher Ebene was ist.
Das passiert einfach dadurch, dass wir dann die einzelnen Abstände
wählen.
Wir haben festgelegt, dass wir fünf Linien zeichnen wollen.
Das heißt, wir berechnen einfach, was die einzelnen Werte sind, die
auf diesen Linien liegen.
Wir berechnen dann auch noch einen Offset mit, den wir dann nachher
brauchen, um einzelne Pixel auszuwählen, auf denen wir die einzelnen
Punkte malen wollen.
Und fangen dann an, die einzelnen Punkte zu malen.
Das heißt, wir malen für jede Farbe, also für jeden x-Wert 256, für
den wir dann natürlich auch noch den einzelnen Pixel berechnen müssen,
weil der x-Wert nicht genau der Pixel ist, der hier in der Grafik
existiert.
Das heißt, man muss diesen Offset noch mit einberechnen.
Und dann auch noch die y-Werte dazu berechnen.
Das sind dann hier einmal der rote Wert, der grüne Wert und der blaue
Wert.
Und auch da dann mit dem Offset der richtige Wert im
Koordinatensystem, der dann an der Stelle gezeichnet wird.
Und dann zeichnen wir auch noch dann nebenbei, wenn wir eh schon hier
vorbeikommen, auch noch die vertikalen Linien, weil, muss man nicht
nochmal drüber iterieren.
Und haben dann jedes Mal, wenn hier Modulo 50 ist, noch einen
senkrechten Strich.
Gibt es hierzu Fragen?
Ja?
Okay.
Mhm.
Ja.
Gute Frage.
Kann sein, dass es danach wieder geändert wird.
Also die Frage des Kommilitonen war, warum wir da 249 nehmen, weil
dann müssten ja eigentlich die einzelnen Ticks, man verliert beim
ersten Mal keinen, aber danach die folgenden sind dann eigentlich eins
auf.
Und warum das trotzdem funktioniert.
Gute Frage.
In dem Fall muss ich sagen, das hat unser Tutor gebaut, von daher
müsste ich da selber reingucken, was da jetzt genau passiert ist.
Ähm.
Genau.
Ja, genau.
Ja.
Okay, danke.
Die Anmerkung war gerade vom Kommilitonen, ob das an dem Set-Label
-Table liegt.
Ja, diese Labels, die hier unten drunter liegen, sind tatsächlich aus
dieser Label-Table.
Das heißt aber, der Strich, der da ist, ist eigentlich dann kleiner,
also es ist nicht 500.
Der Strich da ist dann halt eins weniger.
Hier wäre es dann schon zwei weniger und da sind es dann drei weniger.
Aber die Labels sind immer noch korrekt an der Stelle.
Ob das jetzt die richtige Lösung ist?
Vielleicht hätte man doch einfach bei Null anfangen können und dann
das Ganze mit 250 machen.
Ja, aber du kannst ja nur eins eingeben.
Also das Modell erlaubt ja die Null nicht.
Deshalb haben wir das so gemacht.
Ja, richtig.
Es ist auch nur eine Darstellungsgeschichte an der Stelle.
Genau.
Sonst noch Fragen zur Benutzeroberfläche?
Gut, dann machen wir mal weiter.
Vielleicht noch zu Dingen, wo es bestimmt mehr Fragen gibt.
Nämlich zu Aufgabe 2.
Machen wir mal noch ein bisschen Diagramme überführen.
Welches Diagramm hatten wir denn?
Wir hatten das Aktivitätsdiagramm.
Und wir wollten diesmal, naja, wir hatten ja festgestellt letztes Mal,
Aktivitätsdiagramme eignen sich vielleicht nicht nur so mäßig dafür,
irgendwelche Zustände zu modellieren.
Machen wir das doch diesmal richtig und modellieren mal direkt ein
Zustandsdiagramm.
Zumindest aus diesem Aktivitätsdiagramm.
Was hatten wir jetzt hier als Beispiel?
Wir hatten diese Kaffeemaschine, die immer den Warten- und
Startbildschirmanzeigen macht, außer man zieht das Stromkabel.
Und wenn die Kaffeetaste gedrückt wird, fängt man an, Kaffee zu brühen
und hat dann zwei unterschiedliche Zustände quasi.
Entweder man drückt nochmal die Taste innerhalb von 30 Sekunden, dann
brüht man den Kaffee fertig.
Wenn diese 30 Sekunden aber ablaufen, ohne dass nochmal die
Kaffeetaste gedrückt wird, dann bricht man das Ganze ab.
Das Gerät piepst und der Fehlerbildschirm wird angezeigt.
Und aus dem Fehlerbildschirm kommt man nur mit der Resettaste zurück.
Und dann aber auch wieder zurück in den Warten- und Startbildschirm.
Was sind da jetzt wohl für mögliche Zustände drin?
Offensichtlich haben wir diesen Warten-Zustand, der den
Startbildschirm anzeigt.
Ob Sie den jetzt genauso benannt haben, ist übrigens egal.
Hauptsache, es ist semantisch in Ordnung, wie Sie die Zustände benannt
haben.
Dann gibt es bei uns jetzt einen Prüfvorgang gestartet Zustand und
einen Prüfvorgang beendet.
Ob das jetzt Prüfvorgang gestartet oder Prüfvorgang fertig machen oder
irgendwie, wie Sie den auch immer benannt haben.
Da gibt es unterschiedlichste Möglichkeiten.
Das ist jetzt nur unsere Lösung an der Stelle.
Und wir haben noch einen Fehlerzustand.
Das heißt, das sind die vier Zustände, die wir jetzt modelliert haben.
Schauen wir uns das Ganze mal an.
Wir starten also mit dem Wartenzustand, weil wir hier oben auch im
Wartenzustand starten.
Und der zeigt halt den Startbildschirm an.
Man könnte jetzt darüber diskutieren, ob man das als Entry oder als Do
-Aktion modelliert.
Das ist beides in Ordnung an der Stelle, würde ich sagen.
Weil das semantisch in dem Fall keinen Unterschied macht.
Wenn wir jetzt in diesem Wartenzustand sind und die Kaffee...
Ich wollte erstmal das Stromkabel machen, weil das ist offensichtlich.
Wenn wir im Wartenzustand das Stromkabel ziehen, dann ist das Ganze
beendet.
Genauso wie im Aktivitätsdiagramm haben wir jetzt hier auch das Ende
des Zustandsdiagramms.
Und mit der anderen Option, die wir hier oben bei der Auswahl haben,
der Kaffeetaste, landen wir in dem Prüfvorgang gestartet.
In dem Fall wird dann das Brühen gestartet.
Und in unserem Fall jetzt noch ein Zeitnehmer zurückgesetzt.
Das war jetzt optional, das ist einfach nur unserer Modellierung
geschuldet.
Wenn Sie das nicht modelliert haben, ist das auch okay.
Auch selbst dann, wenn Sie die Guard mit irgendwas Zeitnehmer oder
After 30 Sekunden oder irgendwie anders modelliert haben, ist auch in
Ordnung.
Nur eigentlich müsste man natürlich diesen Zeitnehmer jedes Mal
zurücksetzen, wenn man in diesen Zustand kommt, weil man muss ja
irgendwie wissen, wie viel Zeit denn jetzt schon abgelaufen ist.
Gut, wenn der Zeitnehmer größer als 30 Sekunden ist, also man 30
Sekunden gewartet hat, dann geht man in diesen Fehlerzustand.
In dem Fall wird dann das Brühen abgebrochen, das Gerät piepst und der
Fehlerbildschirm wird angezeigt.
Und aus diesem Fehlerzustand...
Ah, das war noch die Geschichte.
Vielleicht erstmal eins weiter.
Mit der Reset-Taste kommt man wieder zurück in den Wartezustand.
Man könnte sich jetzt überlegen, eigentlich müsste man ja für jeden
Zustand jetzt auch alle anderen Tasten irgendwie modellieren, weil das
sind ja irgendwie Übergänge zwischen den einzelnen Zuständen.
Das ist im Zustandsdiagramm nicht nötig.
Deswegen ist das Ganze hier auch ausgeraut.
Sobald eine Kante nicht modelliert ist im UML-Zustandsdiagramm, dann
wird davon ausgegangen, dass man einfach in diesem Zustand verweilt.
Das heißt, die mussten diese Kanten dann dort nicht auch noch
einzeichnen.
Wenn Sie das natürlich gemacht haben und es mathematisch korrekt ist,
ist das auch in Ordnung.
So, jetzt haben wir noch die zweiten Fall.
Also nicht, wenn wir hier rüber gehen, sondern hier runter gehen.
Dann wird die Kaffeetaste gedrückt.
Dann wird der Prüfvorgang beendet.
Und mit dem Epsilon-Übergang gehen wir wieder zurück in den
Wartezustand.
An der Stelle, sobald das Prüfen fertig natürlich beendet ist.
Das ist auch schon unsere Lösung für den Zustandsautomaten.
Es gibt hier sicherlich mehrere andere mögliche Lösungen.
Das hängt dann ganz davon ab, ob die Semantik die gleiche ist, die
dort modelliert wird.
Also ob Sie das Aktivitätsdiagramm Valide in ein Zustandsdiagramm
überführt haben, was die Zustände abbildet, die man benötigt an der
Stelle.
Das können wir jetzt aber hier im Allgemeinen wahrscheinlich nicht
alle Varianten klären.
Vielleicht schon, aber...
Weiß nicht, also mir fallen jetzt nicht so furchtbar viele andere ein.
Also man kann sicherlich über die Entry- und Exit-Sachen diskutieren,
aber die Zustände sind eigentlich relativ klar.
Man könnte höchstens den Prüfvorgang beendet Zustand weglassen, oder
anders modellieren den Automaten an der Stelle.
Aber ansonsten gibt es glaube ich nicht so furchtbar viel.
Ihr Kommilitone meint gerade den Prüfvorgang beendet Zustand könnte
man einfach weglassen.
Und da stand auch möglichst wenig Zustände.
Wir haben uns dazu entschieden, den mit reinzunehmen.
Wir hatten auch eine Variante ohne.
Aber wir halten die Modellierung so für sinnvoller in Kombination mit
der Entry-Aktion und dem Übergang.
Deshalb haben wir den mit drin.
Aber ja, man kann ihn auch weglassen.
Ja, genau.
Also das wäre sicherlich auch valide.
Sie haben auch noch eine Anmerkung?
Genau, das ist ein Epsilon-Übergang mit einer Guard davor.
Ja, also eigentlich prinzipiell haben Sie da vollkommen recht.
Also der Kommilitone meint, dann kann der ja einfach nur irgendwann
ausgeführt werden.
Die Semantik sollte aber klar sein an der Stelle mit dem Zeitnehmer
dazu.
Man kann das auch anders modellieren.
Es gibt auch eine Darstellungsweise mit After im UML-Zustandsdiagramm.
Wir haben uns dagegen entschieden, die zu nehmen, weil die sehr nach
einem Methodenaufruf aussieht.
Man sieht dann halt schlecht den Unterschied zwischen, was ist jetzt
hier gerade Zeitnehmer und was ist eigentlich Methode.
Und wir haben es in der Vorlesung tatsächlich noch nicht vorgestellt.
Genau, wir haben es in der Vorlesung noch nicht vorgestellt, so kann
man das auch nicht erwarten.
Deshalb ist es mit einer Guard eigentlich hübscher.
Aber Sie haben natürlich recht, das ist semantisch nicht so
hundertprozentig eindeutig an der Stelle.
Ihr Kommilitone fragt, das Stromkabel, das kann man doch eigentlich
immer ziehen.
Und das ist im Grunde das, was Tobias auch schon gesagt hat.
Man könnte und sollte wahrscheinlich hier noch mehr Übergänge haben.
Das ist jetzt so die Mindestanzahl, die gefordert war.
Und das ist halt auch das, was in dem Aktivitätsdiagramm ersichtlich
ist.
Also das wäre das, was wir erwarten würden für diesen
Zustandsautomaten.
Wenn man das erweitert hat und das semantisch sinnvoll ist, sehr
gerne.
Ansonsten gilt, wie Tobias gesagt hat, was nicht modelliert ist, ist
halt implizit einfach kein Übergang.
Dann kennt man halt in dem Zustand die Aktivität nicht.
Aber es wäre natürlich auch okay, das Ziel Stromkabel von allen
anderen darunter zu ziehen.
Wäre eigentlich sogar hübscher.
Nur weil wir es nicht fordern, haben wir es jetzt hier nicht in der
Musterlösung.
Sonst noch Fragen?
Die Frage des Kommilitonen war bei dem Epsilon-Übergang, ob die Entry
-Aktion vorher beendet sein muss, bevor dieser Übergang stattfindet.
Meines Wissens muss die Entry-Aktion beendet sein.
Und erst dann kann eine Transition, also ein Übergang, stattfinden.
Weil die Abfolge im Zustandsdiagramm ist immer erst die Entry-Aktion,
dann mögliche Do-Aktion.
Und dann kann eine Transition stattfinden an der Stelle.
Also meine Intuition würde das gleiche sagen, aber ich weiß es ehrlich
gesagt nicht hundertprozentig.
Da müsste ich jetzt auch aus dem Standard nachschauen.
Aber wäre intuitiv irgendwie logisch, das so zu machen.
Sonst bräuchte man noch ein Bullshit-Flag, das sagt, Brühen ist
beendet und dann kann man den Übergang machen, wenn das der Fall sein
sollte.
Ähm, genau.
Sonstige Fragen?
Gut, dann darf Sebastian weiter bespaßen, glaube ich.
Tatsächlich?
Ja.
Ah, Aufgabe 3, stimmt.
Die Aufgabe 3, die eine reine Bonus-Aufgabe war, für zwei Punkte ging
es nochmal um das Geheimnisprinzip.
Und zwar um Geheimnisprinzip-Anwendungen in der Java-Standard
-Bibliothek.
Und wir hatten Ihnen als Aufgabe gegeben, sich mal diese vier Klassen
bzw.
Schnittstellen in der Java-Standard-Bibliothek anzuschauen.
Und zwar Closable, Comparable, List und Locale und jeweils zu sagen,
was dort eigentlich verborgen wird, welches Geheimnis und wie das
umgesetzt wird.
Und für diese beiden Sachen zusammen gab es dann halt immer Punkte.
Wir haben das hier relativ ausführlich gemacht, also wenn Sie da
einfach nur kürzer mit zwei Stichpunkten das beantwortet haben, ist
das wahrscheinlich auch okay.
Also davon jetzt nicht abstrecken lassen.
Ich wollte es bloß möglichst ausführlich jetzt hier für die Übung
haben.
Und was hätten wir so ungefähr erwartet?
Zum Beispiel für Closable, da geht es darum, stromartige Ressourcen
wegzukapseln bzw.
deren Implementierung.
Das heißt, man möchte gerne eine abstrakte Sicht auf all diese
stromartigen Ressourcen.
Es geht hier nicht um konkrete Systemressourcen.
Auch die Ressourcen, die dahinter versteckt werden, hinter dieser
Schnittstelle, sind auch immer noch keine konkreten Ressourcen.
Das heißt also, wir sind hier immer noch ein Abstraktionslevel drüber.
Wir kapseln im Grunde oder wir sammeln nochmal eine Menge von
stromartigen Ressourcen zusammen und möchten uns da nicht darum
kümmern, wie dann eigentlich das Schließen dieser Ströme funktioniert.
Und das passiert jetzt genau bei dem Close.
Das heißt, dieser Zugriff auf die Ressource wird eben abstray bzw.
das Schließen.
Das ist schon alles.
Das heißt also, bei der Benutzung ist es völlig egal, um welche
konkrete Ressource es sich handelt.
Solange das irgendwas Stromartiges ist, was sich schließen lässt,
können wir die an der Stelle verwenden.
Das wäre schon alles zu Closable.
Ich denke, das ist klar.
Bei Comparable wird was anderes verborgen, und zwar nämlich das
Vergleichen an sich.
Es ist also völlig egal.
Gibt es noch eine Frage zu dem davor?
Ich habe Stromartig, das sollten Sie in Anführungszeichen verstehen.
Die meisten Sachen, die Closable sind, sind Ströme.
Aber Sie haben natürlich recht, es gibt auch noch andere.
Also das Stromartig ist jetzt hier nicht ausschließend gemeint.
Es gibt natürlich andere Klassen, die auch Closable implementieren.
Ich habe das nur so gemacht, damit es leichter zu verstehen ist.
Das heißt, das dürfen Sie jetzt nicht ausschließlich sehen.
Weiter zu Comparable.
Was wird dort verborgen?
Naja, der eigentliche Vergleichsmechanismus.
Das heißt also, der Algorithmus, wie ich zwei Objekte miteinander
vergleiche.
Solange sie vergleichbar sind, ist mir das dann als Nutzer völlig
egal, wie das Vergleichen passiert.
Und das verstecke ich eben hinter dieser Compare-Methode.
Und damit bin ich auch schon fertig.
Also kann ich vergleichen, wie ich will.
Und wenn ich es benutze, ist mir völlig egal, wie der Comparator
funktioniert.
Die Java Util List, das ist so der Klassiker.
Datenstruktur, die verborgen wird.
Mir ist dann als Nutzer nur noch wichtig, dass ich da eine Liste habe.
Und dass ich halt weiß, es gibt da irgendwie ein nächstes Element zum
Beispiel.
Ich kann Elemente hinzufügen.
Und ich kann welche rausnehmen.
Und mehr muss ich darüber nicht wissen.
Wie das dann intern implementiert ist, wie die Datenstruktur dahinter
aussieht, ist dann völlig egal.
Und ich kann natürlich auch unterschiedliche Listen-Implementierungen
austauschen.
Ich kann dann sagen, okay, meine Arraylist brauche ich gar nicht mehr.
Ich möchte vielleicht lieber eine Linked List haben an der Stelle.
Ist völlig egal, kann ich einfach austauschen und mein Programm
genauso weiter benutzen wie vorher.
Weil ich eben mich auf diese Schnittstelle geeinigt habe.
Gut, und zuletzt noch Locale.
Dort werden Gebiets-Schema-Parameter versteckt.
Das heißt Zeichensätze, Formate und so weiter und so fort.
Und das macht man eben, indem man die in einem Objekt kapselt.
Und das dann halt einmal festlegt.
Bei der Benutzung welches das Locale-Objekt einmal festlegt.
Und dann halt immer im Grunde in der Klienten-Klasse alles gleich
machen kann.
Egal in welchem Gebiet, mit welchen Gebiets-Parametern man sich gerade
befindet.
Und das erinnert so ein bisschen an die Strategie.
Aufpassen, das ist hier kein klassisches Strategie-Muster, was hier
verwendet wird.
Aber in der Benutzung ist im Grunde genau der gleiche Vorteil gegeben,
wie wenn ich jetzt eine Strategie hätte an der Stelle.
Gut, das war die Bonus-Aufgabe zum Geheimnis-Prinzip.
Gibt es da Fragen dazu?
Ja?
Ja, sie haben natürlich...
Ja, genau.
Sie haben natürlich für irgendein Recht, das passiert jetzt eigentlich
nicht in der Locale.
Ja, aber die Idee ist glaube ich trotzdem klar, dass das...
Also, ihr Kommilitone beschwert sich gerade, dass die Locale ja an
sich gar nichts macht, sondern nur die eigentlichen Locales, die man
dann benutzt.
Dann hätten wir die halt alle listen müssen.
Also, wir hatten gehofft, dass das von dort aus klar ist, was gemeint
ist.
Wenn wir sie da verwirrt haben, dann tut uns das leid, aber ihre
Anmerkung ist natürlich inhaltlich eigentlich vollkommen richtig.
Da passiert jetzt an sich natürlich nicht viel.
Okay, gut.
Wenn es ansonsten keine Fragen mehr gibt, würde ich mal weitermachen
mit der nächsten Aufgabe.
Noch eine Frage?
Ja.
In der Locale.
Also, in den Locale haben sie halt alles, was auf das jeweilige
Gebietsschema zutrifft, versteckt.
Sie kennen das ja, Locale-Einträge auch von ihrem Betriebssystem.
Wenn sie da sagen, ich habe hier englisches Gebiet, dann haben sie zum
Beispiel eine englische Tastatur-Layout und so weiter und so fort.
Das wird in der Locale im Grunde versteckt.
Oder in den Locales.
Gut,
dann würde ich weitermachen.
Wir haben noch zwei Aufgaben, glaube ich.
Die Aufgabe 4.
Ah ja, die Benutztrelation.
Da ging es darum, die Benutztrelation aufzustellen für unser Projekt
und alles darüber hinaus.
Wer hat denn die Aufgabe gemacht?
Okay.
Wer möchte denn mal sagen, wie er an die Lösung rangegangen ist?
Hat da jemand eine coole Strategie entwickelt?
Da sich kein anderer meldet, dürfen Sie gerne auch nochmal.
Ihr Kommilitone sagt, er hat sich einfach den Maven-Dependency-Tree
ausgeben lassen.
Können Sie noch sagen, wie Sie es gemacht haben?
Ihr Kommilitone sagt sogar noch den Befehl dazu.
Maven, Leerzeichen, Dependency, Doppelfunkt Tree.
Damit könnte man sich sowas ausgeben lassen und dann braucht man im
Grunde nur noch abschreiben.
Das ist eine Möglichkeit.
Man kann natürlich sich auch durch die einzelnen Poms durchhangeln.
Dann ist das im Grunde genau das, was dieser Dependency-Tree auch
macht.
Nur hat man es dann halt selber gemacht.
Ich bin mir nicht ganz sicher, ob der Dependency-Tree alle ausgibt
oder ob man die Art der Dependency noch angeben kann.
Ich habe es nämlich selber auch nicht damit gemacht.
Wir zeigen jetzt mal, was wir erwartet hätten.
Also in der A ging es ja erstmal darum, zu sagen, welche Module hängen
wie zusammen in dem, was wir selber implementiert haben.
Da ging es um die Sachen, die auf den ersten Übungs-Pattern 1 bis 4
gelöst werden sollte.
Und wie das voneinander abhängt.
Wir haben ja gesagt, sie sollen einmal eine Dependency-Kante, also
eine Uses-Kante, so wie man das kennt aus der Benutz-Relation, bauen
und einmal eine für Tests mit Use-T, die zwar eigentlich nicht
irgendeiner Spezifikation genügt, aber wir haben gesagt, das ist
einfacher, als es irgendwie anders zu machen.
Wir haben das hier noch ein bisschen feiner aufgegliedert.
Und zwar danach, ob die Aufgabe irgendwie eine Bonus-Aufgabe vorkam
oder nicht.
Und Test, wie gesagt, und außerdem noch, ob das als optional in der
POM drinsteht oder nicht.
Und da bin ich mir nicht so ganz sicher, was der Maven-Dependency-Tree
an der Stelle macht.
Aber das kann man ja dann gegebenenfalls nochmal ausprobieren.
Wie sollte das jetzt aussehen?
Wir haben hier unser IMAGE-Elter-Projekt.
Darin haben wir im ersten Übungsblatt erstmal dieses DameJRST-Main
-Modul angelegt.
Auf dem zweiten Blatt kam dann das HD-Rise dazu und das Kommando
-Zeilen -Schnittstellen-Werkzeug.
Und das hat offensichtlich eine Abhängigkeit auf das HD-Rise.
Auf Blatt 3 kam dann der Einschub Instagram dazu und das Bundle.
Instagram hat natürlich eine Abhängigkeit auf DameJRST-Main.
Und das Bundle hat beide, also DameJRST-Main als auch Plugin als
Abhängigkeit, als das braucht, um ausgeführt werden zu können.
Blatt 4, da kam dann noch der EyeCatcher dazu als grafische Benutzer.
Oberfläche und der hat offensichtlich eine Abhängigkeit auf das HD
-Rise, weil die Informationen, die da drin stecken, bzw.
der Algorithmus, der wird ja verwendet in der Oberfläche.
So, da gibt es eine Frage.
Nochmal bitte die Frage.
Ist das transitiv genug?
Ja, das ist vollkommen richtig.
Aber das Bundle benutzt ja direkt DameJRST-Main.
Tut es das?
Ja, ich glaube schon, oder?
Aber ich wollte gerade nicht mehr vor Augen, ob das...
Es steht als Dependency im Bundle drin.
Ah, ok.
Ja, dann erledigt sich die Frage.
Also, wenn es da als Dependency drin steht, dann ist es tatsächlich
so.
Ich glaube, du hast recht.
Ich glaube schon, ja.
Können wir nochmal nachprüfen, falls das nicht der Fall sein sollte.
Also, wenn da nur das Instagram-Plugin drin steht und das wiederum auf
Main eine Abhängigkeit hat, dann ist das natürlich richtig.
Dann wäre diese Kante hier überflüssig.
Also, Transitivität müssen wir jetzt hier nicht noch explizit
aufschreiben.
Sonst würde es jetzt gleich auch bei den externen Abhängigkeiten
explodieren.
Das wollen wir natürlich nicht.
So, das war dann auch schon die komplette Aufgabe A.
Haben das alle so hinbekommen?
Ja, alle mal nicken.
Sehr schön.
Dann machen wir jetzt weiter mit den externen Abhängigkeiten.
Dann haben wir erstmal die nicht so externe Abhängigkeit
HarderiseBase.
Das war ja nichts, was sie selber gebaut haben.
Deshalb gehört das eigentlich erst in den Aufgabenteil B.
Wenn das jemand schon bei der A mit reingezeichnet hat, weil es ja
auch eigentlich hier in diesem Projekt drin lebt, dann ist das auch
völlig okay.
Es gibt auf jeden Fall diese Abhängigkeit von Harderise auf das
HarderiseBase, wo die Schnittstellen und Implementierungen, die wir
hier vorgegeben haben, drin leben in diesem Modul.
Und ja, das ist so die eine, die wir vorgegeben haben.
Tobias hat das herausgefunden?
Genau, das Bundle hat direkte Dependencies zu Instagram und JMJSTMain.
Also Nachtrag, diese Kante hier oben stimmt.
Ich war bei der Abhängigkeit vom Kommando-Zeilen-Werkzeug auf
CommandsKey.
Das ist irgendwie offensichtlich, dass man das braucht.
Kann man ja auch sehen in der POM.
Dann haben wir hier oben für das Instagram-Plugin noch das Meta-Inf
-Services gebraucht.
Und hier sehen wir schon, das ist als optional gekennzeichnet in der
POM.
Aber es ist natürlich trotzdem eine Abhängigkeit.
Dann haben wir ganz viele Abhängigkeiten natürlich auf JUnit, weil das
irgendwie jeder benutzt.
Sowohl unser Harderise als auch das JMJSTMain haben wir ja mal Tests
geschrieben.
Deshalb brauchen wir dann da auch unsere Abhängigkeit auf JUnit.
Und CommandsKey hat natürlich auch eine Abhängigkeit auf JUnit.
Ja, Sie haben eine Frage?
Ja, das ist schon richtig.
Wir hatten im Aufgabenblatt, glaube ich, geschrieben, dass wir wissen
wollen, von wem wir alles abhängen.
Ich glaube, da war es irgendwie so formuliert, falls man irgendwie
Lizenzen kaufen müsste usw.
Das heißt, da muss man dann schon auch sehen, wie die weiteren
Abhängigkeiten sind.
Also, dass wir hier von CommandsKey auch noch auf JUnit weiterkommen.
Und Sie werden sehen, so furchtbar viel ist das auch gar nicht.
Also, das hätte man schon weiter verfolgen können.
So, was haben wir noch?
JUnit an sich hat noch eine Abhängigkeit auf HemcrestCore.
Und dann haben wir unsere HarderiseBase.
Wer da mal reingeschaut hat, da haben wir einmal diese Oyalgo Mathe
Bibliothek für Matrizenberechnung.
Dann haben wir Commons Imaging.
Und Commons Imaging selber hängt wieder von CommonsIO ab.
Und hier oben haben wir dann wieder ganz viele Abhängigkeiten auf
JUnit.
Außer Oyalgo, das benutzt diese JUnit Jupyter API bzw.
Engine und Params.
Und von da aus könnte man natürlich jetzt auch nochmal weitergehen,
aber das soll dann genügen an der Stelle.
Also, das würde uns so ausreichen.
Gut.
Gibt es sonst noch Fragen, außer dass sich Ihr Kommilitone beschwert,
dass er da noch weitere Kanten hätte verfolgen müssen?
Sie können da auch gerne weitermachen.
Also, so ist es nicht.
Ihr Kommilitone fragt, ob Sie einfach aufhören sollen, wenn Sie keine
Lust mehr haben.
So ist es natürlich nicht.
Wir haben das jetzt hier so gebaut, dass man die Sachen, die man auf
jeden Fall hätte nachverfolgen können und einfach hätte nachverfolgen
können, noch mit reinbringt.
Und da, wo es dann viel unkompliziert wird, haben wir aufgehört.
Das ist unsere Lösung gewesen.
Sie können da gerne noch weitermachen.
Sie haben noch eine Frage.
Die machen wir gleich noch.
Das Problem ist an der Stelle, dass die Sachen bis dahin, wenn Sie das
über die POMs machen, ist relativ einfach, weil das haben Sie auch
alles lokal.
Aber hier sieht es so aus, dadurch dass das nur als Test eingetragen
ist, haben Sie das normalerweise gar nicht mehr, die einzelnen Module
auf Ihrem Rechner, also gecached.
Und deshalb haben wir gesagt, wir erwarten von hier aus nur noch das,
weil man sieht aus der POM, dass hier die Abhängigkeit herrscht.
Aber von dort aus kann man es eben eigentlich nicht mehr
weiterverfolgen.
Deshalb haben wir an der Stelle aufgehört.
Sie hatten noch eine Frage?
Schon beantwortet?
Okay.
Gut.
Sonst noch Fragen an der Stelle?
Vielleicht noch ein kurzer Nachtrag zu der Transitivität.
Also der Punkt an der Stelle von den Benutzrelationen und warum wir
das modellieren, ist ja herauszufinden, welche Module welche anderen
Module benutzen.
Der Punkt mit der Transitivität war eher so etwas, wir wissen jetzt,
also gehen wir noch eine Folie zurück, dass HDRI's Base Oyalgo und
Oyalgo wiederum...
Oder mach's mit Hamcrest Core, das ist einfacher.
Welches?
Hamcrest Core, ja genau.
HDRI's benutzt JUnit und JUnit benutzt Hamcrest Core.
Man muss jetzt aber hier nicht noch eine Kante von HDRI's zu Hamcrest
Core eintragen, obwohl da Transitivität herrscht.
Weil das ist der Punkt, weshalb man da jetzt nicht noch eine, also
theoretisch benutzt, könnte HDRI's Hamcrest Core indirekt über JUnit
benutzen.
Das ist diese Transitivität.
Aber diese Kante war nicht gefordert.
Der Unterschied zu der Kante jetzt hier oben bei Bundle und JMJRST
Main und Instagram ist, dass Bundle ja trotzdem direkt JMJRST Main
benutzt und man diese Relation auch modellieren möchte an der Stelle.
Es ist ja nicht so, dass Bundle JMJRST Main über Instagram benutzt,
sondern direkt.
Und das ist der Unterschied an der Stelle, warum man jetzt diese Kante
da zeichnen muss und im anderen Fall nicht.
Nur, dass das klar wird.
So, was haben wir noch gehabt?
Aufgabe 5, Entwurfsmuster.
Mal sehen, ob es da eh nicht viele Diskussionen gibt.
Ich bin gespannt.
Wir hatten Ihnen Szenarien vorgegeben und gesagt, bitte finden Sie
doch mal heraus, was denn jetzt hier eigentlich für Entwurfsmuster
sinnvoll wären anzuwenden, um eine sinnvolle Lösung für dieses
beschriebene Problem zu finden.
Und hier in der ersten Aufgabe war es, denke ich, relativ eindeutig.
Da wurde beschrieben, wir haben irgendwie zwei feste Schnittstellen,
die passen aber nicht zusammen.
Jetzt müssen wir da irgendwie zwei Welten zusammenbringen und das war
ja genau unser Einstiegsbeispiel für die Entwurfsmuster.
Das heißt also, hier braucht man irgendwie einen Adapter.
Und hier war auch schon vorgegeben, man könnte zum Beispiel diesen
Primitive Dance Door benutzen.
Wir haben aber inzwischen herausgefunden, es gibt noch eine andere
Möglichkeit.
Wie hieß die andere Klasse?
Primitive Matrix.
Primitive Matrix, genau.
In Oyalgo, die könnte man also auch verwenden.
Ist aber egal, die leisten fast das gleiche, außer dass man, wenn man
den Primitive Dance Door benutzt, noch andere Dinge tun muss.
Wie Sie das also gelöst haben, wenn Sie die Primitive Matrix genommen
haben stattdessen, ist das auch völlig in Ordnung.
Wir haben jetzt den Primitive Dance Door adaptiert.
Und so sieht das dann aus.
Wir hatten ja hier unsere Interface mit dem Matrix Calculator, um den
ging es ja.
Und wir haben dort Inverse, Multiply und Transpose.
Und wir haben auf der anderen Seite den Primitive Dance Door, der uns
zumindest Transpose und Multiply anbietet.
Und jetzt müssen wir halt sehen, dass wir die Schnittstellen
adaptieren und die Schwierigkeit an der Stelle ist, dass der Primitive
Dance Door bei seinen Aktionen, bei seinen Methoden immer davon
ausgeht, dass er selber der linke Teil der Matrix ist.
Das heißt, in diesem Primitive Dance Door werden Matrizen gespeichert
und für die Berechnung ist dann immer der Dance Door selber die linke
Seite der Berechnung.
Das heißt also, für Multiply müssen wir dann bloß noch eine rechte
Matrix übergeben und dann wird die eben mit dem Dance Door selber
multipliziert.
Und in unserer Schnittstelle sieht das eben anders aus.
Das heißt, was wir also machen müssen, ist wir müssen aus unserer
übergebenen Matrix, zum Beispiel bei Multiply, müssen wir erstmal
einen Dance Door bauen und setzen den dann auf unsere Instanzvariable.
Also wir bauen im Grunde in unserem Adapter das so um, dass wir selber
aussehen, auch wie so ein Dance Door, selber ein Klassenattribut haben
mit dem Primitive Dance Door, so dass wir selber im Grunde auch wie
eine linke Seite von einer Berechnung aussehen und dann setzen wir das
halt so.
Da muss man noch ein bisschen umbauen das Ganze in der Methode und
dann bauen wir wiederum für die Matrix B auch einen Dance Door und
dann rufen wir Multiply auf.
Ihr Kommilitone hat eine Frage.
Vielleicht warten Sie eine Folie.
Genau.
Ihr Kommilitone fragt, das ist ein wenig aufgebracht, wie invertieren
Sie denn jetzt?
Deshalb habe ich die Primitive Matrix genommen und damit genau das
nicht passiert, habe ich auch schon vorneweg gesagt, dass es völlig
okay ist, die Primitive Matrix zu nehmen.
Okay.
Also Ihr Kommilitone meint, er hat deshalb die Matrix adaptiert.
Das haben wir tatsächlich auch schon getan in unserer Musterlösung.
Das kann man zusätzlich auch noch tun.
Dann haben Sie ja aber die Matrix Operation halt nicht adaptiert
genommen.
Das ist halt die Frage, wie Sie das dann lösen.
Wenn Ihre Lösung das bietet, dann ist alles gut.
Ich weiß jetzt gerade nicht, wie es funktionieren sollte, wenn ich nur
die Matrix adaptiere.
Deshalb müsste ich Ihre Lösung jetzt sehen, um da was zu sagen zu
können.
Man kann es auch mit dem Dance Door noch lösen, wenn man einfach einen
zweiten Adapter baut.
Und in diesem Adapter einen Inverter Task benutzt.
Im Grunde haben wir hier zwei adaptierte Klassen.
Wir haben hier den Adapter, der aber wiederum zwei Schnittstellen
adaptiert.
Und dieser Inverter Task, der kann invertieren.
Da kann ich auch einfach einen Primitive Dance Door übergeben und
bekomme wiederum ein Matrix Objekt zurück.
Das würde also funktionieren.
Man könnte es aber wie gesagt auch mit der Primitive Matrix machen.
Dann hat man hier nur eine Klasse, die man adaptieren muss.
Sonst noch Fragen bis hierher?
Gut.
Dann Entwurfsmuster B.
Da ging es darum, die Methode Create HDR in dem HDR Combine ein
bisschen flexibler zu gestalten.
Da passieren ja Dinge.
Da werden Gewichte berechnet und dann werden die Farbwerte
ausgerechnet für die einzelnen Pixel.
Und so dann eben die drei Bilder, die wir als Eingabe bekommen haben,
kombiniert.
Und jetzt war die Aufgabe, das Ganze wie gesagt flexibler zu
gestalten, dass da noch andere Dinge passieren können.
Zum Beispiel soll einmal das Calculate Weights nicht ausgerechnet
werden, sondern es soll einfach ein festes Array zurückgegeben werden.
Und dann sollte es noch eine Variante mit einem Blurfilter geben.
Und für solche Situationen bieten sich eigentlich immer zwei
Entwurfsmuster an.
Und zwar entweder eine Strategie oder eine Schablonenmethode.
Wäre hier an der Stelle ratsam gewesen.
Schöner an der Stelle wäre wahrscheinlich sogar eine Strategie.
Aber man hat dann ein bisschen mehr Arbeit.
Deshalb habe ich faulerweise eine...
Vielleicht mal gerade, wer hat denn alles die Strategie genommen?
Und wer hat alles die Schablone genommen?
Das ist so ungefähr ein Drittel, zwei Drittel.
Wir haben beides genommen.
Ihr Kommilitone meinte gerade, wenn man die Strategie benutzt, müsste
man wahrscheinlich dann eher sogar einen Dekorierer bauen.
Damit das richtig funktioniert, weil man ja gegebenenfalls Blur und
diese Calculate Weights, die alternative Variante haben möchte.
Muss ich gerade überlegen, ob das wirklich der Fall ist.
Nicht wirklich, oder?
Man kann ja einfach zwei Strategien bauen.
Eine Blur-Strategie und eine...
Jaja, jetzt ist natürlich die Frage, ob das hübsch ist, aber nötig ist
sicherlich ein Dekorierer nicht.
Man könnte zwei Strategien bauen.
Eine Blur-Strategie und eine für Gewichtung.
Und dann halt die so setzen, wie man es halt haben möchte im Kontext.
Das heißt also, auch nicht so eine furchtbar hübsche Variante.
Deshalb haben wir jetzt hier die Schablonen-Methode gebaut, die finde
ich allerdings an der Stelle auch nur so halb gut passt.
Wie würde man das bauen, wenn man hier eine Schablonen-Methode hätte?
Man würde sagen, das Create HDR, das ist im Grunde die Methode
weiterhin, die wir nach außen tragen.
Und das ist unsere Schablonen-Methode.
Die hat einmal einen festen Bestandteil, der immer ausgeführt wird,
nämlich das Combine.
Hier sehen wir, das ist der Teil, der immer gleich gemacht wird.
Und dann haben wir dort zwei abstrakte Methoden, CalculateWeights und
das Blur.
Und die kommen dann mit in den Algorithmus rein.
Das heißt also, Create HDR macht erst CalculateWeights, Combine und
dann gegebenenfalls Blur.
Und dann haben wir zwei konkrete Unterklassen, die jeweils
CalculateWeights und Blur unterschiedlich implementieren.
In dieser Variante hier, mit dem HDR CombineWithWeights, dort wäre es
dann so, dass die Blur einfach nichts tut.
Die gibt dann einfach nur das Bild wieder genauso zurück.
Da sehen wir aber auch schon wunderbar den Nachteil von Schablonen
-Methoden.
Wenn wir zu viele Unterscheidungen und zu viele Kombinationen haben,
was die Funktionalität anbelangt, dann bekommen wir wieder das
Problem, dass wir sehr, sehr viele Unterklassenbildungen haben.
Das heißt also, Schablonen-Methoden immer mit Vorsicht zu genießen.
Das ist eigentlich nur dann wirklich hilfreich, wenn wir einen
Algorithmus haben, der sich stark ändern kann oder wo es einfach sehr
viele auszutauschende Elemente gibt.
Aber die Kombinationen an sich von diesen Austauschungen, die sind
jetzt nicht so furchtbar groß.
Das ist eigentlich nur der sinnvolle Anwendungsfall.
Das heißt also, wenn Sie hier sehr viele Varianten haben von möglichen
Verhalten, dann wird es mit einer Schablonen-Methode eher nicht mehr
so hübsch.
Gut.
Da gibt es noch eine Frage.
Ich wende mal kurz noch die Sachen ein, dann können Sie Ihre Frage
stellen.
Mit einer Brücke.
Ihr Kommilitone schlägt eine Brücke vor.
Könnte man hier eine Brücke sinnvoll bauen?
Also erstmal würde ich behaupten, dass eine Brücke an der Stelle ein
bisschen Kanone auf Spatze wäre.
Ich überlege gerade, wie haben Sie denn die Brücke gebaut?
Aha.
Okay.
Ja, jetzt habe ich es glaube ich verstanden.
Ihr Kommilitone schlägt vor für die Brücke.
Auf der linken Seite einmal das Blur- und das Calculate Weights
Verhalten als Abstraktion zu bauen und auf der rechten Seite dann die
Implementierung jeweils in den Kombinationen, so wie man das haben
möchte.
Das ist sicherlich eine Möglichkeit und es liegt hauptsächlich daran,
dass ja die Brücke eigentlich nur eine Erweiterung ist von der
Strategie.
Sie haben ja in der Brücke im Grunde von links nach rechts auch eine
Strategie.
Wenn Sie den linken oberen Teil von der Brücke nehmen und die ganze
rechte Seite, dann ist es ja im Grunde genau wie eine Strategie.
Sie haben den Klienten, der ein Interface benutzt und dort sind
unterschiedliche Implementierungen drunter.
Das heißt, das könnte man so interpretieren.
Sie hätten dann halt in Ihrer Brückenvariante noch die konkreten
Implementierungen für die Anwendung, so dass die Kombinationen so
zusammengebaut werden, dass ich im Grunde meine Strategien schon
anwende.
Sie hätten also dann im Grunde konkrete Klienten noch dazu gebaut.
Das heißt also, das würde wahrscheinlich sogar funktionieren.
Müsste ich mir nochmal im Detail anschauen, aber klingt für mich jetzt
erstmal im ersten Moment sogar nachvollziehbar, dass man das so bauen
könnte.
Ich finde es immer noch ein bisschen übertrieben, aber ich glaube, das
ginge sogar.
Ich bin mir nicht ganz sicher, ob das okay ist, wenn man die linke
Seite auch schon eine Implementierung vorgibt und ob man die nicht
eigentlich auch dann auf die rechte Seite der Brücke liefern müsste.
Achso, du meinst...
Vom Prinzip der Brücke her.
Das ist das Einzige, wo ich jetzt... da müsste ich mal noch genauer
drüber nachdenken.
Okay, sonst noch jemand ein Vorschlag für ein hübsches Muster, was wir
nicht gezeigt haben?
Jemand einen Besucher gebaut?
Nein.
Okay, dürfen Sie ja jetzt auf dem Übungsblatt.
Gut.
Letzte Variante.
Ich befürchte, hier gibt es die meisten Diskussionen.
Ich bin mal gespannt.
Hier sollte es darum gehen, unterschiedliche Filter in iMatch noch
einzubauen, sodass man nach der Anwendung der HDR-Erzeugung noch sowas
wie ein Sepia-Filter oder Graustufen oder Weichzeichner drüber laufen
lassen kann.
Aber an der Stelle sollte es nicht darum gehen, wie die
Filteranwendung funktioniert, sondern wie diese Informationen
gespeichert werden.
Und das soll eben möglichst dynamisch funktionieren, dass im Grunde
die Zusammensetzung dieser Informationen, die aus den Filtern kommen,
wie sich die jetzt niederschlägt in der HDR-Bildklasse, das soll im
Grunde dynamisch zusammengesetzt werden.
Und dann haben wir noch so ein Beispiel mitgegeben, dass zum Beispiel
die String-Methode die Namen der ausgeführten Filter ausgeben kann und
diese letzten zwei Sachen mit dem dynamischen Ausführen und mit der
String -Methode.
Das sollte eigentlich schon so ein starker Hinweis sein auf die Sache,
die in Headfirst-Design-Patterns vorgeschlagen wird für das Dekorierer
-Pattern.
Das ist nämlich genau das Beispiel, was dort gebracht wird.
Dort werden dynamisch allerdings Cafés zusammengebaut und dann steht
da halt drin, welche Zutaten da drin waren.
Und so könnte man das jetzt hier machen für unser Bild.
Und das Hübsche, was dann da rauskommen würde, wenn man das so machen
würde, wäre, dass man dieses Bild unterschiedlich dekorieren kann mit
zusätzlichen Methoden, die dann dazu führen, dass wir in dieser einen
Bildklasse im Grunde die Informationen von allen Filtern drinstehen
haben.
Das heißt also, wenn ich hier mit meinem Grayscale-Decorator den Sepia
-Decorator und den Blur-Decorator zusammenstecke und dann auf ein
normales HDR-Bild draufstecke, dann kann ich mir immer von allen
Zwischenzuständen die Informationen rausholen.
Das heißt also, ich kann mir dann das Grayscale-Value rausholen, ich
kann mir das Sepia-Value rausholen.
Das ist jetzt vielleicht nicht so ein gutes Beispiel, weil die ja eher
sich gegenseitig sogar noch beeinflussen würden.
Und ich könnte mir eben die einzelnen Darstellungen für die Pixel
holen, statt mir da unterschiedliche Klassen anzulegen zu müssen.
Ich kann mir natürlich aber auch einfach nur das Endergebnis geben
lassen.
Die haben noch eine Anmerkung dazu.
Die machen unterschiedliche Dinge.
Warum sollte man die nicht schachteln können?
Da muss ich gerade überlegen, wie wir das uns gedacht haben.
Also ihr Kommilitonen merkt an, man könnte die dann nicht
zusammenstecken, weil die ja dann nur das eigentliche Value
zurückgeben, wenn das dort aufgerufen wird.
Ja doch, genau so war es natürlich auch eigentlich gedacht.
Wahrscheinlich.
Also ihr Kommilitone merkt an, dass es der Idee des Dekorierers
widerspricht, wenn ich dann auf dem Get-Value nicht den aktuellen Wert
zurückbekomme, sondern wieder den Ursprungswert, weil ich dann wieder
die Klassen einzeln wissen müsste.
Das ist richtig.
Ich überlege gerade, was wir uns dabei gedacht haben, als wir das so
modelliert haben.
Naja, ich meine durch die dynamische Bindung und die Klasse, die man
gerade als letztes hat, kriegt man halt im Get-Value den Wert, den man
an der Stelle haben will, oder?
Also wenn man jetzt den Grayscale-Decorator als letzten hat, kriegt
man halt den Grayscale-Wert von Get-Value zurück.
Genau, also dann bräuchte man sie ja nicht als Schnittstellen-Methoden
zum Dekorieren.
Sie dekorieren dann zwar, aber dann bräuchte man sie halt nicht noch
extra als Schnittstellen-Methode.
Richtig.
Ja, also das ist jetzt auch nichts, was man jetzt hätte modellieren
müssen.
Das ändert aber nichts daran, dass man an der Stelle trotzdem den
Dekorierer benutzen kann und das Get-Value benutzen kann.
Man muss es halt richtig zusammenstecken.
Man kann jetzt darüber diskutieren, dass man diese Get-Sepia-Value
-Methode überhaupt nicht braucht.
Also zumindest nicht als öffentliche Methode.
Da gebe ich Ihnen auf jeden Fall recht.
Also in dem Get-Value soll eigentlich die Sachen zusammengesteckt
werden.
Das heißt, man hätte die wahrscheinlich dann dieses Get-Sepia-Value,
wenn überhaupt, als private Methode, die eben genau dafür sorgt, dass
jetzt der Anteil, der aus dem Filter rauskommt, noch dazu dekoriert
wird auf das, was wir schon vorher hatten.
Genau.
Gut.
Sind wir doch noch zusammen auf eine gemeinsame Lösung gekommen, mit
der wir alle leben können.
Sonst noch Fragen zum Dekorierer?
Hat jemand was komplett anderes gebaut?
Hat jemand eine andere Idee gehabt?
Vielleicht haben wir was vergessen.
Ja, was haben Sie für eine Idee?
Ein Fliegengewicht, legt der Kommilitone vor.
Da habe ich ehrlich gesagt gerade gar keine Idee, wie man das
zusammenbauen sollte mit einem Fliegengewicht.
Was haben Sie sich denn dabei gedacht?
Okay.
Also Ihr Kommilitone sagt, man möchte dynamisch zusammensitzen und man
bekommt den Output jeweils von den Filtern und man hat dann
wahrscheinlich am Anfang das Fliegengewicht und baut dann die Sachen
drauf.
Aber so richtig weiß ich noch nicht so ganz, wie das funktionieren
soll.
Aber vielleicht fehlt mir da auch die Fantasie.
Da müsste ich mir mal Ihre Lösung anschauen.
Ich wüsste jetzt persönlich spontan nicht, wie ich es damit löse.
Hat noch jemand was anderes gebaut?
Die haben noch eine Idee gehabt?
Als Kompositum hat es Ihr Kommilitone gemacht.
Muss ich mal kurz überlegen, ob was ist.
Das ist halt genau ohne die beiden Methoden, oder?
Genau.
Also als Kompositum würde es wahrscheinlich so ähnlich aussehen.
Ja.
Das wäre dann, denke ich, okay.
Gibt es irgendwas, was dagegen spricht?
Nur semantisch halt, weil Kompositum halt keine Funktionalität
hinzufügen sollte.
Wobei jetzt wieder die Frage ist, wenn wir nur die Get-Value haben und
nicht die anderen beiden, ob dann wirklich Funktionalität hinzugefügt
wird.
Also ich glaube, das kann man definitiv auch so sehen.
Ja.
Also die Frage Ihres Kommilitonen war gerade, ob nicht der Sinn vom
Kompositum ist, es möglichst wenig Klassen.
Beim Kompositum würden Unterklassen gebildet werden, werden ja hier
aber auch im Dekorierer.
Also das Kompositum sieht genauso aus wie der Dekorierer, nur dass der
Dekorierer unten noch weitere Funktionalität hinzufügt.
Vom Muster her.
Von daher...
Da gibt es noch zwei andere Anmerkungen.
Macht aber im Endeffekt der Dekorierer ja auch.
Also der Dekorierer modelliert ja quasi auch eine Baumstruktur an der
Stelle.
Also die Semantik ist sicherlich eine andere, das ist richtig.
Die Frage ist halt, ob jetzt in diesem konkreten Fall tatsächlich was
anders aussehen würde.
Und ich vermute wahrscheinlich sogar fast eher nicht.
Okay.
Noch jemand eine Idee?
Reicht auch, oder?
Gut.
Okay.
Dann sind wir tatsächlich durch.
Vielen Dank.