Wissen Sie noch, was ein Blutscreen of Death ist?
Die Windows 95, Windows 98 Benutzer dürften das wahrscheinlich öfter
gesehen haben, wenn der komplette Bildschirm blau wird.
So schlimm war es jetzt nicht, allerdings ganz ohne war das auch
nicht.
Also dieses Arcon-Gerät, ich hatte schon gesagt, wofür das alles
verwendet wurde.
Es kann beatmen, das heißt, es ist auch etwas lebenskritisches.
Wenn so ein System ausfällt, dann kann das natürlich blöd enden.
Wenn ein paar Ärzte drumherum stehen im Krankenhaus, auf der
Intensivstation oder im OP-Saal, dann können die vielleicht noch
eingreifen, wenn sie es merken, dass das System ausfällt.
Dann können sie dann beatmen mit diesen Pumpen, die sie aus Emergency
Room und Co.
kennen.
Aber wenn nicht, ist es halt blöd.
Es ist ein sehr kritisches System.
Wie schon gesagt, es kann Narkosemittel verabreichen und wird halt
auch während chirurgischen Eingriffen verwendet.
Was war jetzt das Problem?
Naja, 2012 wurde dieses Video gemacht.
Das System hat sich auch sehr gut verkauft.
In vielen Krankenhäusern, Intensivstationen, OP-Sälen wurde es
eingesetzt.
Dann gab es aber eine Rückrufaktion von der FDA.
FDA ist eine Behörde in den Vereinigten Staaten, die überwacht und
zertifiziert solche medizinischen Systeme.
Das heißt, wenn Sie so ein System oder Medikamente auf den Markt
bringen wollen, brauchen Sie eine Zulassung von dieser FDA.
Und die hat davor gewarnt, es kann zu einem Ausfall dieses
Beatmungssystems kommen.
Oder es kann sogar komplett ausfallen.
Was natürlich bei solchen Systemen nicht wirklich gut ist.
Jetzt, woran lag es?
Es lag nicht an der Mechanik, sondern es lag an einem Softwarefehler.
Was war das Problem?
Es gab eine Begründung, die sie veröffentlicht haben.
Ich lese mal diesen dick gedruckten Teil vor.
Dieser Softwarefehler könnte das System stoppen und manuelles
Ventilieren benötigen.
Bitte aufladen.
Gut, das wird nicht der Standardfall sein.
Aber diese Systeme sind nicht nur in OP-Sälen, sondern auch auf
Intensivstationen.
Wenn da ein Angehöriger ist und er wartet 2-3 Stunden und macht sich
Sorgen um den Patienten, der noch nicht wieder wach ist, dann kann es
sein, ich schreibe ein paar WhatsApp-Nachrichten, dann twitter ich ein
bisschen und dann ist mein Akku leer.
Wo lade ich den?
Cool, da sind vier USB-Buchsen.
Nehme ich.
Ja, diese Software war leider nicht so ganz gut programmiert.
Das hat dazu geführt, dass sich das ganze System aufgehängt hat, als
jemand sein Handy angeschlossen hat.
Damit hat wohl kein Programmierer gerechnet.
Also was sollen Sie da mitnehmen, wenn Sie kritische Software machen?
So etwas wie Flugzeug oder hier im Gesundheitsbereich.
Versuchen Sie auch, Sachen zu bedenken, auf die Sie gar nicht kommen.
Was passiert, wenn nicht in USB einfach nur ein medizinisches Gerät
angeschlossen wird?
Was ist, wenn einer eine Xbox-Alter hat, der anschließt eine Wii, ein
Handy oder sonst irgendeinen USB-Ventilator?
Wenn sich dann die Beatmung abschaltet, nur weil einer einen USB
-Ventilator anschließt, wäre vielleicht nicht ganz so gut.
Aber es ist nichts passiert, muss man dazu sagen.
Es ist keiner gestorben, also es ging noch mal glimpflich aus.
Gut.
Dann würde ich beginnen mit der Übung.
Aufgabe 1.
Viele haben sich beschwert, wir machen ja alles auf Deutsch und
Englisch ist doch viel toller und die Fachbegriffe sollten doch
Englisch sein, weil man redet doch später eh nur mit diesen englischen
Begriffen.
Sie kennen vielleicht die Einstellung von Professor Tichy dazu, wir
bringen Ihnen nicht nur Deutsch bei, wir bringen Ihnen auch Deutsch
bei.
Das heißt, Sie sehen bei uns immer die deutschen und englischen
Begriffe.
Zugegeben, die deutschen klingen oft ein bisschen holprig oder lustig,
aber dafür, wenn Sie sie bei Google eingeben, finden Sie unsere Folien
immer als ersten Treffer, weil wir sie fast exklusiv haben.
Wir haben mal versucht, Ihrem Wunsch etwas entgegenzukommen und
perfektes Englisch zu verwenden.
Das ist dieses Kauderwelsch, was da rauskam.
Wir hatten Ihnen vier Sätze gegeben und Sie sollten identifizieren,
welchen Architekturstil aus der Vorlesung wir damit gemeint haben.
Ich glaube, der erste war auch schon der schwierigste.
Ich habe einige Lösungen gesehen, die dort Klient-Dienstgeber haben.
Wer hat bei der Klient-Dienstgeber als Lösung?
Da sind doch schon einige.
Wer hat dienstorientierte Architektur?
Wenige.
Der Unterschied bei kleinen Servern ist noch nicht gesagt, wie sich
das Ganze verteilt.
Sie können in Ihrer Firma einen Server stehen haben und den Client
direkt daneben oder 10 Meter weiter.
Bei einer dienstorientierten Architektur ist es so gedacht, dass Sie
irgendeine gewisse Funktionalität kapseln, als Service verfügbar
machen und den nach außen freigeben.
Oft über so eine Cloud oder übers Internet, über irgendeinen Abnehmer.
Die Sachen, die in diesem Satz wichtig waren, war sowohl die
Functionality, darauf hinaus, dass es eine gekapselte Dienstleistung
sein soll, als auch in der Cloud.
Cloud ist immer so ein schwammiger Begriff, irgendwo liegt das Ganze.
Könnte natürlich auch zwei Meter daneben liegen.
Das heißt, Client-Dienstgeber geht uns da nicht weit genug.
Darum dienstorientierte Architektur.
Der zweite Satz war, wir müssen den Throughput unseres Tools
maximieren.
Das Processing sollte separated und einzelne Parts parallel prozesst
werden.
Damit war Fließband gemeint, falls Sie es erraten konnten.
Also, Sie möchten den Durchsatz erhöhen.
Wie wollen Sie das machen?
Indem Sie einzelne Stücke möglichst parallel ausführen.
Das ist eben gerade die Idee hinter so einem Fließband.
Dann ging es hier darum, Sie möchten eine GUI machen, die sollte aber
getrennt entwickelt werden von der Funktionalität.
Das heißt, wenn Sie etwas mit Grafik zu tun haben, das möchten Sie
vielleicht später an irgendeinen Designer auslagern, der das Ganze
hübsch macht, während die Informatiker das Ganze funktionell richtig
machen.
Das heißt, Sie möchten hier eine Trennung haben, zwischen der macht
das, der macht das und am Schluss fügt man das zusammen.
Und dieses Architekturstil begünstigt das Ganze.
Also Model-View-Controller, wenn Sie das englische Wort haben wollen.
Das begünstigt das Ganze, weil Sie da eben eine gewisse Trennung
haben.
Wissen Sie, gute Software funktioniert wie ein gut geführtes
Unternehmen.
Wir Manager oben sagen, was zu tun ist, Projektleiter planen und
Entwickler setzen um.
Und die melden sich nur, wenn sie fertig sind.
Sie sehen, das sind so verschiedene Hierarchie-Ebenen und das Ganze
geht von oben nach unten.
Das ist eben bei einer Schichtenarchitektur auch der Fall.
Dieser letzte Satz, und die melden sich nur, wenn sie fertig sind.
Was könnten wir denn damit gemeint haben?
Genau, ich sage es nochmal, Sie rufen jetzt nicht aktiv nach oben
wieder irgendwas auf, weil das wäre bei einer Schichtenarchitektur
auch nicht erlaubt, sondern Sie geben einfach nur so Callbacks zur
Rückmeldung nach oben.
Genau, also das würde die Schichtenarchitektur nicht verletzen, so
asynchrone Callbacks.
Gut, das wäre auch schon die 1a gewesen.
Dann sollten Sie sich in der 1b entscheiden, ob Sie diese
Bildberechnung lieber mit einer Klient-Dienstgeber-Architektur oder
mit Partnernetzen realisieren wollten.
Sollten das dann auch begründen, indem Sie Vorteile von Ihrer Wahl
angeben und dann noch sagen, auf welchem System die Bilder dann
berechnet werden und wann und zwischen wem dann die Kommunikation
stattfinden würde.
Das heißt, wie Sie sich entscheiden, ist uns erstmal egal, Sie müssen
das bloß irgendwie vertreten können.
Es gibt für beides Vor- und Nachteile, wobei man dann natürlich
trotzdem darüber streiten kann, ob es nicht sinnvoller ist, das jetzt
so oder so zu machen.
Prinzipiell, Sie sollten irgendwie ein paar Vorteile nennen.
Wir haben jetzt hier mal so ein paar wenige auf die Folie gemacht.
Bei einem Klient-Dienstgeber ist es relativ einfach, wer für was
zuständig ist.
Der Server macht halt die Berechnung und der Dienstgeber kriegt das
Ergebnis.
Hat auch den Vorteil, dass die Ressourcen zentral von einem
Dienstgeber bereitgestellt werden müssen und halt auch von dem
gewartet werden.
Also Sie haben nicht 100 Leute, die halt auch 100 einsatzfähige
Rechner haben müssen, wie es bei einem Peer-to-Peer-Netzwerk
beispielsweise der Fall ist und jeder muss dieselbe Software-Version
drauf haben und dafür Sorge tragen, dass das Ganze funktioniert.
Wenn Sie dann eine neue Software-Version ausrollen wollen, müssen Sie
überall das Update installieren.
Das haben Sie hier nicht.
Wir haben hier mal so ein bisschen Datenschutz-Besser- noch in
Anführungszeichen.
Naja, wenn Ihr Bild auf einem zentralen Server eines Anbieters liegt,
ist der Datenschutz vielleicht besser zu handeln, als wenn es jetzt
bei 30 anonymen Leuten übers Internet verteilt auf Ihrem Rechner
liegt.
Ist halt die Frage, ob man da jetzt seine Privatfotos, die man
irgendwie bearbeiten will, hinschicken möchte.
Bei Partnernetzen gibt es auch ein paar Vorteile.
Wenn so ein Server halt von einem Dienstgeber überlastet ist, naja,
dann wird Ihre Bildberechnung nicht stattfinden oder langsam oder
verzögert stattfinden.
Bei Partnernetzen könnte man dann vielleicht auf einen anderen Partner
wechseln.
Das heißt, da haben Sie keinen Flaschenhals durch diesen Dienstgeber.
Das Ganze ist dezentral und redundant verfügbar.
Das heißt, wenn einer ausfällt, wie schon gesagt, können Sie
vielleicht auf einen anderen wechseln oder Sie können, wenn Sie
mehrere Bilder zu bearbeiten haben, vielleicht ein Bild dorthin
schicken, ein Bild dorthin schicken und dann wird das Ganze
gleichzeitig bearbeitet von verschiedenen Partnern und Sie sammeln
dann die Ergebnisse wieder ein.
Das soll nicht heißen, dass Sie ein Bild nehmen, das
auseinanderschneiden und jeder berechnet irgendwie die Hälfte oder ein
Viertel, aber wenn Sie mehrere Bilder an sich hätten und mehrere
Filter anwenden wollten, können Sie das Ganze vielleicht mit
Partnernetzen auch machen.
Wie schon gesagt, das sind jetzt nur ein paar Vorschläge.
Wenn Sie dann noch andere gute Gründe hatten, dann ist das auch okay.
Das wären nicht so die, die uns eingefallen wären.
Gut.
Wo findet die Bildberechnung dann statt und zwischen wem gibt es
Kommunikation?
Naja, bei Klient-Dienstgebern ist die Bildberechnung eben auf der
Dienstgeberseite und der Dienstnehmer, der Klient, bekommt dann eben
das Ergebnisbild.
Kommunikation findet zwischen den beiden statt.
Der Klient schickt das Originalbild an den Dienstgeber und kriegt dann
das Ergebnisbild oder das Vorschaubild oder was auch immer er haben
will oder das Zwischenbild eben vom Dienstgeber zurück.
Bei Partnernetzen würde man sein Bild einem oder mehreren Partnern
schicken, je nachdem, was man haben will.
Man könnte auch sagen, ich schicke ein und dasselbe Bild an 10 Partner
und der, der es mir am schnellsten zurückschickt, von dem nehme ich
es.
Die anderen 9 haben umsonst gerechnet.
Könnte man machen.
Wäre zumindest von der Architektur her möglich.
Kommunikation, ja, sie senden halt das Originalbild dann an diesen
einen oder an die mehreren Partner und kriegen dann von denen eben die
Ergebnisse auch wieder zurück.
Gibt es Fragen zur ersten Aufgabe?
Wie fanden Sie denn das Englisch aus dem A-Teil?
Sehr gut, einen Daumen nach oben.
Wenn Sie mal neben einem Manager stehen, einem BWLer, der versucht IT
zu reden, kommt so ein bisschen was in der Richtung.
Wenn Sie Glück haben, weiß er, wovon er redet.
Wenn nicht, haben Sie zumindest ein paar Passwörter gelernt.
Also Passwörter sind immer wichtig.
Bei uns ist es auf jeden Fall ein Top.
Gut, Aufgabe 2.
Sie sollten einen Iterator für Plugins entwickeln, der zusätzlich eine
Sortierung der Plugins sicherstellt und das Entwurfsmuster-Iterator
einsetzt.
Im Aufgaben-Teil A sollten Sie dazu ein UML-Diagramm erstellen und das
dann später im B-Teil umsetzen.
Hier nochmal zur Erinnerung, das ist das Entwurfsmuster-Iterator.
Das stellt einfach eine Möglichkeit zur Verfügung, wie man auf
Elemente von so einer aggregierten Struktur sequentiell zugreifen
kann, ohne eben diese Struktur selbst zu enthüllen.
Das heißt, Sie haben hier links so ein Aggregat und ein konkretes
Aggregat und rechts eben so eine Schnittstelle, die Ihnen so eine
Funktionalität wie AddNextes oder GiveNextes oder EntferneEins aus
dieser Menge von Aggregaten bereitstellt und Sie haben einen Iterator,
über den Sie dann rübergehen können.
Wenn Sie das jetzt in UML umsetzen wollen, für unseren Fall, könnte
das so oder so ähnlich aussehen.
Wir haben hier oben die Schnittstelle Iterator mit so einem JMJ-RST
-Plugin, das eben genau diese aufgezählten Methoden zur Verfügung
stellt und Sie haben hier eine Schnittstelle, die so einen Iterator
zur Verfügung stellt.
Wir hatten Ihnen gesagt, Sie sollen GetPlugins verwenden und sollen
dort ein iterierbares Objekt zurückgeben, anstatt der Liste, die Sie
zuvor hatten.
Wir haben hier diesen Plugin-Container und diesen Plugin-Iterator.
Der Plugin-Iterator speichert Sie auch noch die Position, also wenn
Sie da jetzt sequenziell durchgehen, dann wird intern gespeichert, bei
welchem Element Sie sind, damit wenn Sie nachher Next aufrufen, eben
aus der Menge das nächste herausgenommen werden kann.
Und Sie haben hier diesen Container.
Hier sind nochmal die entsprechenden Ausdrücke des allgemeinen
Iterator -Entwurfsmusters und hier haben wir noch so ein bisschen mehr
Einblicke.
Wir haben gesagt, Sie sollen das Ganze sortieren.
Jetzt bietet es sich an, dass Sie das gleich im Konstruktor sortieren.
Wo Sie das machen, ist jetzt ein Stück weit egal.
Sie können das entweder wenn Sie einen Plugin-Container anlegen und
Ihnen die Liste übergeben, das Ganze gleich sortieren.
Oder Sie könnten die Sortierung auch hier drin machen im Plugin
-Iterator erst.
Bleibt Ihnen überlassen.
Wir hatten, glaube ich, geschrieben implementieren Sie einen Iterator,
der die Sortierung der Plugins sicherstellt.
Also ob Sie es jetzt hier reinmachen oder hier, ist uns im Endeffekt
relativ egal.
Sie sollten bloß an irgendeiner Stelle sich überlegen, dass das Ganze
sortiert werden muss.
Sie sollten es, glaube ich, sortieren und dann auch noch umdrehen.
Das können Sie entweder mit diesen zwei Befehlen machen oder es gibt,
glaube ich, noch einen anderen, mit dem Sie es in einem Befehl machen
können.
Das Interface in der Standardbibliothek sieht kein Remove vor.
Sagen Sie.
Okay.
Das weiß ich nicht auswendig.
In unserer Implementierung werfen wir einfach eine Exception.
Wird nicht unterstützt, diese Funktion.
Sie müssen das nicht machen.
Wir bestehen da auch nicht drauf.
Brauchen Sie auch nicht.
Ist auch in der Aufgabenstellung nicht gefordert.
Aber ist das wirklich nicht in der Schnittstelle drin?
Das verwundert mich jetzt.
Ist drin, oder?
Meines Erachtens ist es in der Schnittstelle drin.
Okay.
Sie sagen, es war nicht drin.
Ich bin mir nicht hundertprozentig sicher und kann es jetzt nicht
nachprüfen.
Ich bin aber der Meinung, dass es drin sein müsste.
Wer ist der Meinung, oder wer weiß, lieber wer weiß nicht, wer ist der
Meinung, wer weiß, dass diese Remove-Methode in der Schnittstelle drin
ist?
Eine Person, zwei, drei.
Nicht drin.
Okay, wer denkt, dass sie nicht drin ist?
Ich weiß nicht, so oder so.
Sie müssen sie nicht implementieren.
Daher ist es uns für die Bepunktung und die Lösung der
Aufgabenstellung egal.
Okay.
Dann sind noch weitere Fragen zu diesem UML-Diagramm.
Sonst würden wir in den Code reinschauen.
Wieso macht man einen Plugin-Container nicht als Unterklasse von?
Können Sie ein bisschen lauter reden?
Von List.
Plugin-Container als Unterklasse von List.
Das verstehe ich jetzt nicht.
Warum möchten Sie das?
Dann hätten Sie die Implementierungsdetails gleich auf dieser Ebene.
Außerdem gibt eigentlich das Emboss-Muster vor, dass Sie hier die
Schnittstelle Iterable implementieren müssen.
Da müsste ich mir Ihre Lösung angucken.
Kann ich mir jetzt nicht so ganz vorstellen.
Aber das ist jetzt halt ein Vorschlag.
Wenn es bei Ihnen auch funktioniert und gut aussieht, kann man das
sicherlich machen.
Ach gut, dann hängen wir uns da dran.
Ich wiederhole es nochmal.
Sie haben gesagt, es gibt die Remove, aber in der Schnittstelle ist
keine Implementierung drin.
In Java 8 wird anscheinend auch diese Exception ausgelöst, die wir
auch haben, wenn man sie nicht selber implementiert und überschreibt.
War noch eine zweite Frage?
Genau dasselbe sagen.
Schönes Zeitalter, jeder guckt auf dem Handy nach.
Gibt es noch weitere Fragen, Anmerkungen?
Alles klar.
Dann gucken wir mal rein.
Wie könnte dieser Plugin-Container aussehen?
Er implementiert diese Iterable-Schnittstelle.
Dann muss er hier diese in grün dargestellte Schnittstelle im Iterator
implementieren.
Da gibt er jetzt eben einen neuen Plugin-Iterator zurück.
Also das, was jetzt auf dem UML-Diagram rechts unten war.
Man sieht hier, wir haben uns für diese Variante entschieden, dass die
Sortierung der Liste im Konstruktor vom Plugin-Container gleich
stattfindet.
Dann ist das Ganze sortiert.
Und wir haben hier diesen Plugin-Iterator als Nested-Glas, also als
Private -Glas innerhalb dieser anderen Klasse gemacht.
Das muss man natürlich nicht machen, das haben wir gemacht, weil
dieser Iterator wird nicht woanders verwendet, also können wir das
auch privat in diese Klasse reinmachen.
Das hat den Vorteil, dass wir innerhalb dieser Klasse auch auf diese
List zugreifen können und wir müssen es auch nicht im Konstruktor hier
übergeben.
Müssen Sie aber nicht so machen, können Sie natürlich auch anders
machen.
Der Plugin-Iterator an sich, ich hatte es schon gesagt, er speichert
sich irgendwie den Index, wo er gerade ist, bei welchem Element.
Beim UML-Diagram hat es schon gesagt, Sie können auch die Sortierung
hier drin machen, muss nicht unbedingt hier oben sein, dann aber
natürlich nicht zweimal machen.
Der Plugin-Iterator an sich, der die Iterator-Schnittstelle
implementiert, muss dementsprechend, also wir haben hier AddOverride,
Remove, aber anscheinend machen wir wohl genau dasselbe, was in Java 8
oben auch gemacht wird.
Die Remove ist uns auch nicht so wichtig, wichtiger sind diese beiden
oberen Methoden, die HasNext und die Next-Methode.
Die HasNext überprüft einfach, ob es noch ein weiteres Element gibt.
Also wenn ich dem Iterator sage, gehe mal sequenziell durch diese
Menge von Daten durch, dann muss er irgendwie nachschauen können, ja
gibt es da überhaupt noch ein nächstes Element oder nicht.
Das heißt, er schaut einfach, ob die Größe von der Liste größer ist
als diese Index-Position, wo er gerade ist.
Und wenn man dann das nächste Element haben will, also gib mir jetzt
das nächste Element in der Liste oder in der Menge, dann schaut er
erstmal nach, ja gibt es da noch eins?
Wenn nicht, naja, dann gibt es halt eben so eine
NoSuchElementException.
Also sprich, nö, gibt es nicht, das ist ein Fehler.
Und wenn doch, dann gebe ich halt eben das Objekt an der Position
zurück und zähle eben diese Variable um 1 hoch.
Gibt es bis dahin Fragen?
Zu dieser Implementierung?
Okay.
Der Plugin-Manager, da hatten wir gesagt, da müssen Sie jetzt dafür
sorgen, dass keine Liste mehr zurückgegeben wird, sondern eben dieses
Iterable.
Das heißt, Sie müssen diese zwei grünen Anpassungen machen.
Sie müssen einfach sagen, naja, okay, ich will so einen neuen Plugin
-Container haben und dem übergebe ich jetzt eben diese Liste von
Plugins, die ich durch diesen Service-Loader bereitgestellt bekomme
und dann kriege ich die sortiert zurück.
Wenn Sie das Ganze jetzt verwenden wollen, haben Sie im Großen und
Ganzen diese zwei Möglichkeiten.
Können Sie so einen Iterator auf dieses Get-Plugins holen und dann
halt eben sagen, ja, okay, solange es ein nächstes Element in dieser
Menge gibt, holen wir das bitte und mach dann irgendwas mit.
Also mit iter.next bekommen Sie dann das Element und dann können Sie
damit weiterarbeiten.
Oder Sie nehmen diese For-Each-Geschichte, diese Vorschleife und
sagen, naja, jedes Plugin, was ich hier finde, möchte ich jetzt
irgendwas machen.
Natürlich ein bisschen bequemer, aber letztendlich Geschmackssache.
Haben Sie Fragen zu dieser Iterator-Aufgabe?
Okay, dann kommen wir schon zur Aufgabe 3.
Da sollten Sie Geometrify umstrukturieren.
Wir hatten gesagt, Sie sollen Geometrify sinnvoll umstrukturieren.
Es soll erweiterbar sein, Sie möchten neue primitive und neue
zugehörige Filter einfach hinzufügen können.
Sie sollen dabei darauf achten, dass es wenig Redundanzen oder
möglichst keine Redundanzen im Quelltext gibt.
Und dann sollten Sie sich überlegen, welchen Wurstmuster sich hierfür
anbieten, an welcher Stelle diese eingesetzt werden sollten und uns
dann auch noch sagen, warum Sie dies als sinnvoll erachten.
Wir haben hier mal das Wort sinnvoll versucht, ein bisschen nochmal
auf eine Folie zu bringen.
Naja, wann ist die Umstrukturierung sinnvoll?
Also ein wichtiger Punkt war uns anscheinend die Erweiterbarkeit.
Was ist, wenn Ihr Chef jetzt kommt und nochmal 10 neue Primitive haben
will?
Keine oder möglichst keine Quelltext-Redundanz.
Sie möchten nicht, dass in jedem Filter beispielsweise immer genau
dasselbe steht.
Und Sie möchten allgemein natürlich sauberen Code schreiben.
Das heißt, es soll leicht lesbar sein, leicht wartbar, es soll sich an
Geheimnisse und Substitutionsprinzip halten und auch sonst eigentlich
ganz hübsch sein.
Was wir uns da überlegt hatten, was Sie vielleicht auch hätten machen
können, wenn Sie es nicht anders gelöst haben.
Sie können sagen, naja, wenn ich jetzt mehrere Filterklassen haben
werde, dann macht es nicht Sinn, wenn in jeder Filterklasse dieselbe
Apply -Methode steht und dieselbe Add-to-Image und Calculate
-Difference.
Vielleicht möchte ich das eine Ebene höher ziehen.
Das heißt, Sie könnten eine neu abstrakte Filterklasse machen, die
eben diese Funktionalität bietet und in den Unterklassen mit Dreieck,
Rechteck machen Sie einfach nur noch Filter, Dreieck- oder Rechteck
-Filter machen Sie einfach nur noch das, was spezifisch ist für dieses
eine Primitiv.
Das Ganze können Sie dann natürlich abstrakt machen, weil Sie möchten
keine Filterklasse implementieren, sondern Sie möchten instanziieren,
denn Sie möchten halt weiterhin einen konkreten Filter instanziieren.
Was wir uns überlegt hatten, was Sie als Entwurfsmuster machen können,
die Apply-Methode könnten Sie als Schablon-Methode machen, in der Sie
die Generate-Primitiv-Methode als Einschub verwenden.
Das heißt, in dieser abstrakten Klasse können Sie die Apply-Methode
machen, in der rufen Sie dann intern auch diese Generierung vom
Primitiv auf, aber welches Primitiv letztendlich generiert werden
soll, das können Sie an eine Unterklasse dirigieren, eben durch so
eine Einschub-Methode.
Generate-Primitiv können Sie zusätzlich als Fabrik-Methode machen, das
heißt, Sie rufen das auf irgendeinem Primitiv auf und das Primitiv
erzeugt dann eben genau ein Objekt dieser jeweiligen Klasse, das Sie
haben wollen, und liefert Ihnen das zurück, eben wie eine Fabrik.
Dann müssten allerdings alle konkreten Filter noch von so einer
abstrakten Filterklasse erben, um halt auch wiederum gemeinsame
Funktionalität zu kapseln und nach oben zu ziehen.
Primitive, auch da können Sie sagen, okay, sowas wie Get-Bounding-Box
oder Set-Bounding-Box, Get-Color, Set-Color, das muss auch nicht jeder
gleich machen, weil das Zurückgeben von der Farbe sieht halt auch
überall gleich aus.
Also machen wir auch ein neues abstraktes Primitiv, so als
Zwischenschicht, und lösen das dann halt eben beispielsweise durch
Vererbung.
Die Isntype-Primitive-Methode ist gleich für Rechteck und Quadrat
sowie für Ellipse und Kreis.
Wir hatten das schon ein bisschen in der Aufgabenstellung angedeutet.
Wir haben von einer Spezialisierung geredet, da bietet sich dann
natürlich Vererbung an, dass Sie sagen, ein Quadrat ist eben eine
Unterklasse von so einem Rechteck, weil die einfach ziemlich viel
gemeinsam haben.
Was allerdings unterschiedlich ist, überall natürlich die
Konstruktoren, die sehen anders aus bei den verschiedenen Primitiven,
das heißt, sie brauchen diese Primitivklassen, die halt eben mal zwei
Punkte, mal drei Punkte entgegennehmen, um das Ganze zu bauen.
Das dann halt irgendwie umsetzen.
Wenn wir das Ganze jetzt als Klassendiagramm mal darstellen wollen,
dann könnte das so aussehen.
Wir haben auf der linken Seite jetzt hier diesen neue abstrakte Klasse
PictureFilter, die halt eben die Dinge, die alle Filterklassen
gemeinsam haben, eine Ebene nach oben zieht.
Die Farbberechnung oder das Zusammenbauen des Bildes.
Was in der unteren Variante, in der konkreten Bildfilterklasse da noch
drin steht, ist halt eben, wie genau dieses Primitiv erzeugt wird und
was es zurückliefert.
Das heißt, sie hätten hier oben die von uns vorgegebenen Abstract
Primitive PictureFilter, die eben auch Generate Primitive anbietet,
implementieren die aber hier in der PictureFilter nicht, sondern erst
hier unten im Triangle PictureFilter und wie sie alle heißen.
Also hier sind ein paar Methoden und Attribute ausgeblendet.
Sie dürfen sich nicht wundern, hier sind nur die wichtigsten drin.
Sie haben eine Frage?
Warum man da zwei abstrakte Klassen macht?
Die hier haben sie von uns vorgegeben, die können sie nicht ändern.
Und die da können sie aber einfügen.
Also die Frage war, warum man das mit zwei abstrakten Klassen macht.
Gut, rechts iPrimitive hatten sie auch von uns vorgegeben.
Ja, das ist halt das Problem.
Sie müssen mit dem arbeiten, was da ist.
Sie können aber natürlich auch Unterklassen bilden.
Und es war auch so ein bisschen im Forum die Frage, ja, das macht ja
Quatsch und warum sollte ich jetzt jeden Filter einzeln machen oder
jedes Primitiv einzeln machen, dann habe ich ja redundanten Code.
Sie haben nur dann redundanten Code, wenn sie den halt nicht
hochziehen in eine gemeinsame Klasse.
Auf der rechten Seite haben sie auch dieses iPrimitive von uns
vorgegeben mit der insidePrimitive-Methode, die sie dann halt auch
konkret eben natürlich für jedes Primitiv implementieren müssen, weil
sie dort jeweils anders prüfen müssen, ob ein Punkt drin liegt oder
nicht.
Das oben hatte ich schon auf der Tonspur gesagt, das in Blau.
Wir sehen hier nochmal, Apply ist die Schablon-Methode.
Wenn sie das in Wurstmuster anschauen, weil Apply ruft eben
GeneratePrimitive auf, aber GeneratePrimitive wird eben erst in der
konkreten Unterklasse implementiert, also mit Leben gefüllt und ist
gleichzeitig Einschub-Methode und Fabrik-Methode, weil es halt in dem
Fall jetzt ein Dreieck mit drei Punkten zurückliefern würde.
Hier nochmal der Hinweis, den ich auch schon gesagt hatte.
Wenn Sie sich jetzt wundern, ja, aber da oben oder hier ist ja noch
mehr drin oder ist in diesem AbstractPrimitive gar nichts drin, ja
doch, Sie sehen es jetzt bloß nicht hier, weil wir die Folie jetzt
nicht überladen wollten.
Das war das, was wir uns bei 3a und 3b gedacht hätten.
Haben Sie zudem noch Fragen?
Also das ist jetzt natürlich eine Möglichkeit.
Wenn Sie jetzt eine perfekte andere Möglichkeit haben, mit der Sie
auch Redundanzen komplett vermieden haben und es sonst irgendwie gut
gemacht haben, dann ist es auch okay.
Das ist jetzt halt eine Möglichkeit, die wir Ihnen anbieten, das zu
lösen.
Was, Observer?
Sie möchten in der PictureFilter-Klasse in der AbstractPrimitive ein
Observable reinbringen?
An welcher Stelle und warum?
Um später Minus3 zu verwenden.
Hörst du jetzt nicht, was ich dagegen spreche?
Ich überlege gerade, ob wir das so gelöst haben.
Naja, wenn ja, dann sehen wir es in ein paar Folien.
Ja, also ich meine, wir haben das sogar in der Musterlösung noch drin,
vom letzten Mal.
Aber das sehen wir gleich auf ein paar Folien weiter.
Sonst noch Fragen?
Dann schauen wir uns die 3C an.
Die 3C war nochmal die Zurückspulen-Funktion, die Sie von Übungsblatt
4 kennen.
Da haben wir gesagt, da hätte sich ein Entwurfsmuster angeboten und
Sie sollen sagen, welches Entwurfsmuster und das dann auch noch
begründen.
Woran wir gedacht hatten, wäre das Memento.
Speichert halt eine Momentaufnahme des Zustandes oder Teilzustandes
eines Objekts.
Man kann darüber streiten, ob das jetzt mit Kanonen auf Spatzen
geschossen ist oder nicht.
Aber das wäre zumindest das Entwurfsmuster, das wir Ihnen in der
Vorlesung gezeigt haben, was da passen würde.
Sie können natürlich auch sagen, wieso speichere ich nicht alle
Primitive in einer Liste und kloppe die dann irgendwie zusammen.
Ja, das geht auch.
Memento hat halt den Vorteil, Sie können da noch mehr drin speichern.
Wenn Sie das ganze System jetzt erweitern wollten, dann wäre das jetzt
relativ leicht, in diesem Memento zu speichern.
Hier kann man jetzt natürlich auch sagen, hätte man auch anders lösen
können.
Das ist jetzt die Memento-Klasse.
Ein Memento speichert einen konkreten Zustand.
Sie sehen, ein konkreter Zustand besteht bei uns aus dem Primitiv, aus
dem aktuellen Primitiv, das man berechnet und als bestes Primitiv
ausgewählt hat.
Wir speichern gleichzeitig noch die Größe des Bildes und den Bildtyp,
den ich dann brauche, um später dieses Ergebnisbild wieder
zusammenzusetzen.
Sie wissen, das Vorschaubild ist kleiner als das Originalbild, darum
ziehen wir hier die Größe mit.
Könnten Sie jetzt aber auch an anderer Stelle natürlich speichern.
Was wir auch brauchen, wir brauchen irgendwie einen Verweis zum
Vorgänger.
Jetzt hier in dem konkreten Beispiel, besteht halt ein Zustand aus dem
aktuellen Primitiv und im Endeffekt aus einer Liste von allen
Primitiven, die es davor gab.
Also sprich, aus allen Zuständen, die es davor gab.
Das heißt, Sie können sagen, okay, denn Snapshot 2 dieses Bildes
besteht aus dem zweiten besten Primitiv, das wir hatten, und aus dem
ersten Primitiv, das wir hatten, und aus einem schwarzen Ergebnisbild.
Wenn Sie das dann zusammenbauen wollen, diesen Zustand 2, dann würden
Sie das schwarze Bild nehmen, würden das Primitiv 1 drauf machen und
das Primitiv 2 drauf machen.
Das heißt, Sie brauchen immer rückwärts den jeweiligen Vorgänger und
der braucht dann wieder auch einen Verweis auf den Vorgänger.
Dann können Sie das rückwärts durchgehen und können den Zustand von 0
bis da, wo Sie es haben wollen, rückwärts wieder aufbauen und auf ein
Ergebnisbild legen.
Ich hoffe, es wurde halbwegs klar, was ich gemeint habe.
Also die Frage war, ob man eine doppeltverkettete Liste auch nehmen
kann, damit man nicht so rückwärts durchgehen muss.
Ja, da hatten wir auch schon darüber diskutiert, ob man statt dem
Vorgänger eben eine Liste mit Nachfolgern speichert, dann kann man das
Ganze natürlich schneller und leichter wiederherstellen.
Ja.
Allerdings besteht der Zustand aus dem aktuellen plus alles, was zuvor
war.
Das heißt, semantisch gesehen speichern Sie das, was zuvor war und
nicht den Ausgangs...
Das ist so ein bisschen wie SVN oder Git, speichere ich jetzt die
erste Version und dann die Inkremente nach vorne oder speichere ich
die letzte und dann die Inkremente rückwärts.
Sie können natürlich beides machen, da haben Sie recht.
Aber semantisch gesehen besteht halt ein Zustand, den ich jetzt habe
aus dem jetzigen plus alles, was zuvor war.
Das heißt, aus dem Gesichtspunkt müsste man das, muss man es halt so
machen.
Wie schon gesagt, Sie können auch alle Primitive in einer Liste
reinkloppen, eine Reihe nach und dann eben wirklich von null bis
hinten die einfach wieder auslesen.
Dann würden Sie es auch so nach vorne machen, aber es macht halt ein
bisschen diesen Gedanken von Zustand, ist halt alles, was bisher war,
plus das Neue macht es kaputt.
Das ist für Ihre Folien.
Im Picture-Filter würden Sie jetzt so eine Methode anbieten, wir haben
sie jetzt Snapshot genannt.
Restauriere Zustand, heißt sie glaube ich im Entwurfsmuster.
Also sprich, Sie möchten jetzt irgendwie Snapshot 20 wiederherstellen.
Wie funktioniert das Ganze?
Sieht natürlich jetzt ein bisschen unschöner aus, da wir jetzt eben
über diese Vorgänger drüber iterieren müssen.
Das heißt, Sie müssen ein Memento übergeben.
Also Sie sagen, ich möchte jetzt Zustand jetzt muss ein Memento
kommen, das halt eben den 20.
Zustand widerspiegelt.
Würden Sie sagen, okay, ich mache mir jetzt erstmal so ein leeres Bild
in eben der richtigen Größe, die ich haben will.
Dann mache ich mir eine Liste, in der ich jetzt alle Primitive, die es
bis dahin gab, zu dem Zustand 20, die es da gab.
Also in die Liste möchte ich jetzt das erste, beste Primitiv, das
zweite, das dritte und so weiter bis zum 20.
reinschreiben und die dann Reihen nach eben auf dieses schwarze Bild
anwenden und dann habe ich halt eben den Zustand restauriert von der
20.
Iteration.
Das heißt, ich würde jetzt eben mir immer den Vorgänger holen, von dem
Vorgänger das beste Primitiv, das ich in dem Memento gespeichert habe,
das in die Liste reinmachen und dann würde ich sagen, naja und jetzt
gehe ich wieder zum nächsten Vorgänger.
Und solange es Vorgänger gibt, also solange ich nicht beim ersten Bild
anfange, bin ich hier in dieser Weilschleife und schreibe einfach alle
Primitive, die in diesen verschiedenen Mementos gespeichert sind, in
diese Liste rein.
Wenn ich das habe, dann habe ich diese Liste mit den Primitiven, die
ich jetzt auf das schwarze Bild zeichnen muss.
Allerdings, wenn ich mit dem Index 20 anfange, dann den Vorgänger
nehmen, 19, dann 18 und 17, dann ist die Liste jetzt einfach falsch
rum.
Wir wollen ja sequenziell das ganze wieder von vorne nach hinten
aufbauen.
Das heißt, ich kann hier, wenn ich will, rückwärts drüber gehen, also
die Vorschleife geht jetzt einfach rückwärts über diese Liste drüber
und dann füge ich einfach eben dieses Primitiv, das ich in der Liste
finde, nach und nach auf dieses Ergebnisbild zu.
Wir haben hier noch ein paar grüne Boxen zur Orientierung.
Ist die Idee klar geworden, wie wir das machen?
Immer über den Vorgänger gehen, sich das Primitiv holen, in die Liste
reinspeichern und die dann rückwärts auslesen und dadurch den Zustand
bis dahin wieder zu restaurieren.
Wenn Sie Fragen haben, gerne.
Dann versuche ich das nochmal umzuformulieren.
Okay, also wie schon gesagt, das ist jetzt nicht unbedingt die
hübscheste Variante, aber so geht es halt, dass Sie über den Vorgänger
drüber iterieren.
Nein, also das erste Mal setzen wir das Previous auf das Memento und
holen uns von dem aktuellen quasi das Primitiv.
Da gibt es keinen Halbpointer.
Gut.
Was haben wir hier?
Wir müssen natürlich diese Mementos irgendwann erstellen.
Das bietet sich natürlich immer dann an, wenn Sie jetzt ein neues,
bestes Primitiv berechnet haben.
Sie haben hier diese Apply-Methode.
Hier hat er diese zwei Vorschleifen, der ist zweimal drüber iteriert.
Dann kommt irgendwann mal dieses Best Primitiv raus, das Sie bisher
immer mit AddToImage nach und nach auf das Bild draufgemacht haben.
Hier an der Stelle können Sie dann sagen, na okay, ich will jetzt ein
neues Memento machen.
In dem neuen Memento gebe ich jetzt das neue Primitiv, das ich
berechnet habe.
Dann diese Daten vom Bild.
Dann brauche ich aber jedes Mal noch einen Verweis auf das Memento
zuvor.
Das allererste Memento hat keinen Vorgänger.
Aber ab dann kann ich sagen, okay, wenn jetzt ein neues Memento
gemacht wird, also in der nächsten Vorschleifen-Iteration, dann sage
ich das, was ich zuletzt hier gerade hinzufüge, das soll für das
nächste Memento halt wiederum der Vorgänger sein.
Und so kriege ich diese Verkettung über diese verschiedenen Vorgänger
-Mementos hin.
Sie sehen hier oben noch dieses Mementos.Clear.
Das kann man vielleicht auch an einer anderen Stelle machen.
Das ist dazu da, dass wenn Sie jetzt den Filter zweimal anwenden, wäre
ja in Ihrer Liste jetzt zweimal alle Mementos gespeichert.
Das heißt, irgendwo müssen Sie die Liste auch wieder leer machen, wenn
Sie das Ganze mehrfach laufen lassen wollen.
Beziehungsweise, wenn Sie jetzt Mementos von den Vorschaubildern auch
machen würden, aus irgendwelchen Gründen, und dann jetzt nochmal mit
dem Run, dann hätten Sie da beide drin, das wäre jetzt auch nicht gut.
Das heißt, an irgendeiner Stelle müssten Sie die Liste auch nochmal
leeren, oder zumindest sicherstellen, dass sie leer ist.
Gibt es Fragen zu diesem Teil des Einbaus?
Gut, dann brauchen Sie das Ganze natürlich noch, da müssen Sie ja
diese Slider noch anzeigen.
Also dieser Slider, an dem Sie wählen können, welche Iterationen Sie
haben wollen.
Das könnten Sie in unserer Image-Processor-Klasse machen, die wir
Ihnen schon vorgestellt hatten.
Da können Sie sagen, immer dann, wenn die Berechnung komplett fertig
ist, also wenn ich jetzt das letzte Bild von dem Filter berechnet
habe, dann setze ich diese Mementos eben, oder wiederhole ich mir die
Mementos von diesem Filter.
Also Sie hatten gesehen, wir haben so eine Get-Mementos-Funktionalität
implementiert.
Und hier unten sagen wir dann, okay, ab sofort ist der Slider enabled,
also ab sofort wird der Slider auch angezeigt, weil jetzt ist die
Berechnung fertig.
Jetzt kann der Benutzer hin und her gehen.
Vorher macht es natürlich keinen Sinn, wenn ich diesen Slider
anschaue, weil sonst zerschießt mir das Ganze.
Also das heißt, in dieser Run-Window-Klasse brauchen wir eben das
Setzen der Mementos und das Aktivieren des Snapshots.
Dann sollte das so oder so ähnlich aussehen.
Auf dem Bild erkennt man sogar, wer das sein sollte.
Sie haben hier unten jetzt diesen Slider.
Das haben wir auch als J-Panel gelöst.
Bietet sich an, können Sie aber auch anders machen.
Besteht im Grunde aus zwei Sachen, einem J-Slider, hier dieses Slider
-Symbol, das Sie schon kennen, und einem Label, das Ihnen einfach
angibt, welchen Snapshot Sie gerade aktuell ausgewählt haben.
Wenn Sie diesen Slider machen wollen und hinzufügen wollen, könnte das
auch so ähnlich aussehen.
Sie brauchen hier den Label, der Ihnen das da anzeigt und halt per
Default auf irgendeinen Wert setzt.
Sie brauchen hier diesen Slider, der einen minimalen und maximalen
Wert hat.
Und dann müssen Sie hier diesen Change-Listener implementieren, der
Ihnen dann halt sagt, was passiert jetzt, wenn jemand an diesem Regler
rumspielt.
Auch hier gibt es wieder, wie letztes Mal auch vorgestellt, dieses if
-not -slider.get-value-is-adjusting.
Das wird aufgerufen, sobald derjenige rumgespielt hat und dann aber
loslässt.
Während er spielt, soll natürlich noch nichts passieren, erst wenn er
dann wirklich eine Reglereinstellung hat.
Dann soll der Label auf den neuen Wert, den er gerade ausgewählt hat,
gesetzt werden.
Und Sie müssen sich hier eben den Snapshot holen, den Sie gerade
anzeigen wollen.
Sie sagen hier, dieses Get-Snapshot, dem müssen Sie ein Memento
übergeben.
Wenn jetzt der Benutzer beispielsweise auswählt, ich möchte jetzt das
20.
Bild wieder herstellen, dann würden Sie jetzt sagen, ich gucke mir
diese Liste von Mementos an und hole mir dort raus das 19.
Element.
Also eine Liste von 0 bis 19 sind dann 20 Elemente, Sie wollen das 20.
haben, also würden Sie mit Index 19 drauf zugreifen.
Dann würden Sie sagen, ich möchte davon ein Snapshot haben, dann wird
hier dieses Output-Bild erzeugt und es wird dann halt einfach als
Preview angezeigt und wie Sie es bisher auch gemacht haben, eben dem
Benutzer präsentiert.
Gibt es Fragen dazu?
Nein?
Okay.
Dann kam die allzeits beliebte Aufgabe 4.
Hier nochmal zur Erinnerung, da sollten Sie konkrete
Umstrukturierungen machen, die Sie in 3a beschrieben haben.
Außerdem sollten Sie Geometrify um die primitive Rechteck-Quadrat,
Ellipse und Kreis erweitern.
Hier sollten Sie auch wieder Redundanzen vermeiden.
Sie sollen gucken, wo Vererbung sich anbietet und Sie sollen
Proprimitiv -Einzüge im zugehörigen Filter erstellen.
Auch hier kam wieder Kritik.
Ich habe Aufgabe 3 gelesen, habe die gemacht und habe bei Aufgabe 4
gesehen, oh ja, ich brauche ja einzelne Klassen pro Primitiv und
Filter.
Wieso kann es nicht sein, dass ich Aufgabe 3 mache und bei Aufgabe 4
lese ich was anderes?
Sie können natürlich chronologisch die Aufgaben bearbeiten, aber wir
würden Ihnen trotzdem empfehlen, das ganze Aufgabenblatt zu lesen.
Wir können natürlich sagen, beachten Sie unsere Hinweise für die
nächste Aufgabe, aber wenn wir das mal nicht machen, kann das
natürlich blöd sein.
Also bitte lesen Sie sich immer das ganze Übungsblatt durch, bevor Sie
mit einer Aufgabe anfangen.
Sie sollten nur Punkte übergeben für diese Konstruktoren.
Und wenn Sie irgendwelche Berechnungen machen müssen, wie Länge, Höhe
und Breite, dann sollen Sie das innerhalb von diesen Primitiven machen
und eben diese Punkte, die Sie im Konstruktor kriegen, dazu verwenden.
Die sollten achsensymmetrisch sein.
Auch da gab es ein bisschen Probleme mit dem Begriff.
Was wir gemeint hatten, hatte man in irgendeinem Forumseintrag dann
nochmal gesagt.
Achsensymmetrisch heißt zur x- und y-Achse parallel.
Das heißt, ein normales Rechteck ist achsensymmetrisch bei uns, wenn
Sie das aber um ein paar Grad drehen, ist es nicht mehr
achsensymmetrisch zur x- und y-Achse.
Innerhalb dieses Rechtecks sind diese einzelnen Teile natürlich
weiterhin symmetrisch.
Aber das war die Bonusaufgabe C, wo Sie ein nicht achsensymmetrisches
Rechteck noch machen sollten.
Gut, hier nochmal die Dinge, die wir uns ja vorgestellt hatten.
Die hatten wir jetzt gerade bei Aufgabe 3 schon gesehen.
Könnte dann so aussehen.
Sie haben hier diese Zwischenstufe Picture Filter, die abstrakt ist,
die eben die Funktionalität von den einzelnen Filtern hochzieht.
Sie haben hier diese genannten Filterklassen, für jedes Primitiv eine.
Sie haben hier auf der rechten Seite diese neun Primitive, Kreis und
Quadrat, eben als Spezialisierung von Ellipse und Rechteck.
Und Sie haben hier oben diese abstrakte Primitiv-Klasse.
Was hier fehlt, die implementiert diese i-Primitiv-Schnittstelle.
Da oben ist eine Frage, ja?
Ganz laut bitte.
Noch lauter.
Muss es was geben?
Sie... nochmal.
Sie möchten...
Das heißt, Sie würden die jeweils in eine Klasse machen, Rechteck und
Square in eine Klasse zusammen, oder wie?
Square ganz weglassen?
Naja, in der Aufgabenstellung steht, Sie sollen eine Square-Klasse und
einen Square-Filter machen.
Also das Problem ist immer, wenn wir was fordern, dann ist der
Diskussionsspielraum immer relativ klein.
Ich sag's mal so.
Man kann darüber streiten, ob es Sinn macht, jetzt das als
Spezialisierung zu machen, oder ob das klanke Unsinn ist, aber wir
lassen uns halt auch gerne Unsinn einfallen, und dann müssen Sie den
halt irgendwie umsetzen.
Also in dem Fall war es jetzt halt konkret gefordert bei uns, dass Sie
das so machen müssen.
Als Spezialisierung...
Irgendwo stand's.
Und auch vermeiden Sie Redundanzen und so weiter.
Gut, also wie schon gesagt, hier oben fällt noch die iPrimitive
-Schnittstelle, die zu implementieren ist.
Können Sie sich einfach dazu denken.
Wenn Sie die Bonusaufgabe machen, dann hängt sich das hier halt eben
noch ein, diese beiden Filter- und Primitiv-Klassen.
Dürfte aber auch jetzt keinen riesen Unterschied machen.
Wie sieht bei uns diese abstrakte Filter-Klasse aus, die wir da
dazwischen ziehen?
Die würde halt eben jetzt diese Apply-Methode und ein paar andere
Methoden eins höher ziehen.
Und wir hatten schon gesagt, dass diese Apply-Methode als Schablon
-Methode dient und halt einen Einschub bekommt.
Dieser Einschub wäre halt eben genau dieses Generate-Primitive, das
dann eben von der Unterklasse erst verwendet wird.
Also der Picture-Filter an sich ist noch abstrakt.
Das heißt, ich muss dann einen konkreten Dreieck-Picture-Filter machen
und der bietet dann die Generate-Primitive-Methode an, die auch
gleichzeitig als Fabrik dient.
Die abstrakte Primitiv-Klasse, die implementiert eben diese
Schnittstelle, was auf der Folie gerade gefehlt hatte und zieht halt
die Funktionalität hoch, die jedes Primitiv gemein hätte.
Also sprich, die Bounding-Box und die Color mit ihren Gettern und
Zettern, die braucht man jetzt natürlich nicht jedes Mal neu
implementieren in der Unterklasse.
Jedes Mal ein Get-Color, was mir einfach ein Color zurückliefert.
Das wären einfach Redundanzen, die man hier nicht haben will.
Und drum zieht man das eben eins höher.
Davon erben natürlich dann alle abstrakten Filter-Klassen.
Jetzt schauen wir uns mal konkret an, wie könnte so eine Ellipse
implementiert sein.
Ich glaube, wir hatten irgendwie gesagt, die Ellipse besteht aus einem
Punkt und dann irgendwie einer Höhe und einer Breite.
Wir hatten noch die Einschränkung, es dürfen aber nur Punkte übergeben
werden in den Konstruktoren.
Das heißt, sie kriegen einmal irgendwie den Mittelpunkt von der
Ellipse und dann noch irgendeinen anderen Punkt und aus diesem anderen
Punkt sollten sie dann eben Höhe und Breite entsprechend berechnen.
Da können sie jetzt sagen, naja gut, ich nehme einfach die zwei X
-Werte, ziehe sie voneinander ab und mache da irgendwie den
Absolutwert davon, weil ich ja nicht weiß, ob er irgendwie links oder
rechts von diesem Mittelpunkt liegt.
Dann hätte ich die Breite und bei der Höhe mache ich dasselbe mit den
Y -Koordinaten und dann kann ich da einfach meine Boundingbox daraus
berechnen, wenn ich dann so eine Ellipse habe.
Nehme ich einfach hier den am weitesten links liegenden X-Wert als für
die obere Kante und den kleinsten Y-Wert und dementsprechend halt
anders und mir halt eben diese Boundingbox zusammenzubasteln.
Ich denke, das dürften sie verstehen, wie das geht.
Ansonsten kann ich da jetzt daraus berechnen, ob ein Punkt in diesem
Primitiv liegt.
Das können sie relativ leicht, wenn sie eine Suchmaschine ihrer Wahl
verwenden, herausfinden oder wenn sie sich mit Mathematik auskennen,
dann sowieso.
Aber da finden sie eigentlich relativ viele Möglichkeiten im Internet.
Es gibt auch oft mehrere Möglichkeiten, wie sie das Ganze überprüfen
können.
Da möchte ich jetzt nicht näher drauf eingehen.
Aber was sie brauchen, sie müssen im Konstruktor eben die Boundingbox
setzen, die sie in irgendeiner Form berechnen müssen und sie müssen
sagen, wie das Ganze geprüft werden kann, ob da ein Punkt jetzt drin
liegt oder nicht.
Der Kreis als Spezialisierung von der Ellipse sieht dann
dementsprechend hübscher aus.
Durch Vererbung reicht es, wenn sie einfach nur im Konstruktor sagen,
ein Kreis hat auch eine Breite und Höhe so wie eine Ellipse auch, aber
da sind Breite und Höhe gleich.
Das heißt, ich nehme einfach den zweiten Hilfspunkt, den ich habe und
sorge dafür, dass ich zweimal dieselben Wert für Breite und Höhe
rausbekomme.
Das heißt, man könnte hier von diesem Punkt einfach zweimal den x-Wert
haben und dann ist halt Breite und Höhe von dieser Ellipse gleich und
dann habe ich halt folglich einen Kreis.
Rechteck dürfte jetzt auch nicht so kompliziert gewesen sein.
Die Boundingbox von einem Rechteck zu berechnen ist jetzt nicht so
schwer.
Und zu schauen, ob das dann da drin liegt, auch nicht.
Da sie ja nicht wissen, ob der Punkt in der Boundingbox liegt, die
jemand da aufruft, müssen sie trotzdem noch gucken, ob der Punkt
irgendwie innerhalb von dieser Boundingbox liegt.
Aber da die Boundingbox so groß ist wie ihr Primitiv, können sie das
eigentlich relativ schick machen.
Und wenn sie jetzt hier das Quadrat als Spezialisierung von Rechtecken
haben, können sie das auch so machen.
Auch hier müssen sie halt wieder gucken, dass beide Seiten gleich
sind.
Können sie dann auch über diesen zweiten Hilfspunkt machen.
Können sie sich, wenn sie wollen, zu Hause nochmal genauer durchlesen.
Wie sieht jetzt so eine Picturefilter-Klasse aus?
Also so eine konkrete Filterklasse hier auch wieder an der Ellipse.
Die muss einfach dieses GeneratePrimitiv anbieten.
Also diese Einschubmethode und Fabrikmethode, die eben ein neues
Objekt dieser zugehörigen Filterklasse zurückliefert.
Also in dem Fall sollte der EllipsePictureFilter eben halt auch eine
Ellipse zurückliefern.
Eine Ellipse braucht in dem Fall zwei Punkte, um diese Breite und Höhe
zu berechnen.
Das heißt, sie würden hier eine neue Ellipse mit eben zwei zufällig
erzeugten Punkten zurückgeben.
Das hat man schon gesagt.
Das hier war die Bonusaufgabe.
Also so ungefähr stellten wir uns so ein Non-Axis-Aligned Rechteck
vor.
Auch hier will ich Ihnen jetzt nicht die komplette Mathematik
vergaukeln.
Das ist aber unter den Top 3-4 Treffern in Google, wenn Sie danach
suchen.
Man hat zwei Punkte gegeben und braucht einen Hilfspunkt.
Der Hilfspunkt P muss jetzt nicht unbedingt hier in der Mitte zwischen
C und D liegen.
Der kann auch woanders liegen.
Aber das ist das, was Sie hier mit dem Lambda und Gedöns alles machen.
Sie berechnen sich jetzt eben einen Punkt C und einen Punkt D dazu.
Wenn Sie den gefunden haben, schauen Sie sich an, welcher von diesen
vier Punkten den ich jetzt habe, hat den minimalsten X-Wert.
Welcher hat den minimalsten Y-Wert.
Das wäre dann irgendwie hier oben.
Dann würde hier oben Ihre Boundingbox anfangen und hier unten irgendwo
enden.
Dann hätten Sie Ihre Boundingbox.
Die Berechnung, ob ein Punkt innen drin ist oder nicht, sieht so aus.
Auch das können Sie sich, wenn Sie wollen, zu Hause mal angucken.
Oder auch nicht.
Haben Sie Fragen zur Aufgabe 4, außer zu was ist die Mathematik
dahinter?
Okay.
Dann haben Sie es fast geschafft.
Eine Aufgabe fehlt noch.
Da ging es um die Erweiterung dieser grafischen Benutzeroberfläche.
Ihr Chef meinte, es können in Zukunft noch weitere Primitive
hinzukommen und jetzt möchte ich die Primitivfilter dynamisch zur
Laufzeit laden können.
Sie hatten ja schon die Serviceloader-API.
Nutzen Sie die und jetzt machen Sie mal so eine Dropdown-Liste, in der
ich auswählen kann, welchen Filter ich jetzt haben will.
Wenn ich den ausgewählt habe, soll dann das Vorschaubild entsprechend
diesem ausgewählten Filter erzeugt werden, damit ich mir das besser
vorstellen kann.
Wenn ich dann auf Run klicke, soll eben auch das Bild mit diesem
ausgewählten Filter erstellt werden.
Zusätzlich soll das Ganze noch alphabetisch sortiert werden, bevor es
in diese Klappliste reinkommt.
Und alle verfügbaren Abstract Primitive Picture-Filter sollten geladen
werden.
Also es war vorgegeben, was Sie da genau zu laden haben.
So könnte das aussehen.
Sie müssen irgendwie die Filter laden.
Das heißt, Sie benutzen hier diese Serviceloader-Geschichte, die Sie
schon kennengelernt haben.
Sie müssen halt sagen, was geladen werden soll.
Wir haben hier diese Picture-Filter, also diese eine Stufe unter den
Abstract Primitive Picture-Filter.
Er soll alle Picture-Filter, die er findet, laden und in so einen
Vektor reinknallen.
Also so eine Liste.
Da muss das Ganze noch irgendwie sortiert werden.
Es war gefordert, dass das Ganze alphabetisch sortiert wird.
Das heißt, Sie können mit Collection Sort sagen, was.
Also mit Collection Sort können Sie sortieren.
Sie können sagen, ich möchte hier diese Filters sortieren.
Und dann können Sie mit so einem Komparator angeben, naja, wie sieht
denn jetzt so ein Vergleich aus?
Also wie genau möchte ich sortieren?
Was Sie hier sehen, Sie sagen, okay, ich überschreibe hier in diesem
neuen Comparator für Picture-Filter.
Überschreibe ich jetzt die Compare-Methode und sage, wie genau jetzt
sortiert werden soll.
Ich übergebe zwei Filter und sage, naja, wenn der Simple Name, also
der Kurzname einer Klasse, ohne das Punkt Java hinten, wenn der
Kurzname einer Klasse verglichen mit einem anderen irgendwie minus 1,
0 oder 1 ist, dann sortiert es so und so.
Also so funktioniert auch ein normaler Vergleich von Zeichenketten.
Da vergleichen Sie auch zwei Zeichenketten miteinander und dann gibt
es halt minus 1, 0 oder ein 1 und dementsprechend sind die beiden
Einträge halt gleich oder das eine ist in Anführungszeichen kleiner,
also alphabetisch davor oder halt eben größer.
Das heißt, hier wäre der Trick, dass Sie halt einfach irgendwie über
den Namen gehen.
Wobei es hier eigentlich egal sein dürfte, ob Sie jetzt den Simple
Name nehmen oder halt den normalen Namen mit Punkt Java hinten dran
oder mit Punkt irgendwas hinten dran.
Einfach, Sie müssen irgendwie die Namen von diesen Klassen
herausfinden und miteinander vergleichen.
Das wäre eine Möglichkeit, wie Sie es machen können.
Ja, wenn es halt keine Filter gibt, dann ist es blöd.
Dann gibt es halt hier eine Ausnahme.
Wie sieht das Ganze grafisch aus?
Sie haben jetzt hier diese Klappliste.
Wenn Sie da draufklicken, dann sehen Sie jetzt halt hier, was es so
alles gibt.
Das sind diese Kurznamen hier, das heißt, ohne Dateiendung, so wie man
es vielleicht haben will.
Man könnte vielleicht auch noch das Filter wegmachen, wenn es einen
stört.
Oder den Picture-Filter wegmachen, wenn es einen stört.
Und dann wählt man was aus und wenn was ausgewählt wird, soll eben
hier dieses Vorschau-Bild aktualisiert werden mit dem jeweiligen
Primitiv und bei Run sollte der dann halt auch verwendet werden.
Das heißt, Sie müssen irgendwo eben diese Dropdown-Box einbauen.
Das heißt, wir haben es in so einem Config-Pane als eigene Methode
drin.
Wie sieht jetzt diese Methode aus?
Sie brauchen diese Dropdown-Liste, das können Sie mit so einer J-Combo
-Box machen, die gefüllt wird mit allen zur Verfügung stehenden
Filtern.
Dann können Sie, das sieht man gleich auf der nächsten Folie, das
Ganze noch in irgendeiner Form hübsch machen.
Müssen Sie jetzt natürlich nicht für die Aufgabenstellung, aber da
können Sie jetzt halt Farbe sagen und wie sehen die Einträge genau aus
in so einer Zelle, in so einer Auswahl.
Und dann natürlich noch ganz wichtig, was passiert, wenn jetzt der
Benutzer eins von diesen Filterklassen auswählt?
Naja, dann soll irgendwas gemacht werden und zwar auf jeden Fall
müssen wir im Hintergrund irgendwo sagen, der und der Filter ist
ausgewählt, damit der später auch verwendet wird, wenn einer auf Run
klickt.
Das heißt, Sie würden hier sich das aktuell ausgewählte Item holen und
das dann halt eben übergeben an Ihre App, die halt eben speichert,
welcher Filter da jetzt ausgewählt ist.
Und dann müssen Sie halt irgendwie das Vorschaubild nochmal neu
generieren, das hatten wir Ihnen schon mal gezeigt, das haben wir in
diese Update-Preview-Methode ausgelagert, die müssen Sie dann halt
auch neu aufrufen, sobald ein neuer Filter ausgewählt wurde, damit das
Ding neu berechnet wird.
Dann können Sie, wenn Sie wollen, da noch ein bisschen hin und her
schieben, damit es noch irgendwie in der Mitte ist, diese Dropdown
-Box, wie Sie wollen.
Ich zeige Ihnen mal, wie Sie das Ganze noch hübsch machen können.
Wie schon gesagt, war jetzt nicht gefordert, kann auch hässlich
bleiben.
Sie, also den Label, sprich den Eintrag an sich, den Sie jetzt hier
gesehen hatten, also in dem Fall hier diese Einträge,
CirclePictureFilter, die können Sie auch wieder über den SimpleName
sich holen von diesen durch den ServiceLoader geladenen Klassen.
Dann können Sie noch sagen, okay, wenn einer ausgewählt ist, dann
möchte ich irgendwie eine vordefinierte Vordergrundfarbe haben, also
dieses Blau, was Sie gesehen haben, ist in der Windows-Welt ja so ein
bisschen dominanter, wenn was ausgewählt ist.
Ansonsten ist es wahrscheinlich irgendwie weiß.
Das können Sie hier mit diesem Label SetForeground und Background
machen, aber kann man machen, muss man natürlich nicht.
Gut, da noch mal die Erklärung.
Jetzt sind wir heute relativ früh dran.
Wollen Sie noch Vorlesung?
Wer will Vorlesung?
2, 3, 4, 5, 6, die Alternative ist gehen.
Wer ist für gehen?
Sie haben Glück, ich habe keine Vorlesung vorbereitet, daher sage ich
machen wir den demokratischen Ansatz.
Dann begrüße ich Sie nächsten Montag wieder, Professor Tichy in voller
Frische.
Schönes Wochenende.