Was machen wir heute?
Kurz objektorientierten Entwurf.
Ich habe ja das letzte Mal schon angekündigt, viel ist es nicht mehr,
was da noch on top kommt, zum modularen Entwurf.
Tatsächlich wird es hier mehr so um eine Abgrenzung noch oder eine
Überführung von dem einen in das andere handeln.
Viel mehr ist es tatsächlich gar nicht.
Und dann werden wir uns heute mit Architekturstilen beschäftigen und
wenn dann noch Zeit ist, schon die ersten Entwurfsmuster anschauen.
So, auf geht's.
Objektorientierter Entwurf.
Ich hoffe, das Wissen vom modularen Entwurf ist noch frisch.
Und das werden wir jetzt wieder brauchen.
Hier nochmal der Hinweis gleich zu Beginn.
Im objektorientierten Entwurf bleibt erstmal alles genauso wie im
modularen Entwurf.
Alle Prinzipien, die wir dort definiert haben, bleiben auch hier
gültig.
Und hier wird das Ganze noch ein bisschen erweitert, dadurch, dass wir
mehr Möglichkeiten haben.
Gleich bleibt aber vor allem, dass auch hier Schnittstellen die
Entwurfsentscheidungen verbergen sollen, die geändert werden können
sollen.
Denken Sie da wieder an das Geheimnisprinzip.
Auch das ist zentral für den objektorientierten Entwurf.
So, wir hatten Unterschieden beim modularen Entwurf nach externen
Entwurf und unteren Entwurf.
So schauen wir uns das jetzt auch nochmal kurz an für den
objektorientierten Entwurf.
Wir hatten das letzte Mal schon so ein bisschen drüber diskutiert mit
einem Kommilitonen.
Was ist denn jetzt eigentlich das Modul im objektorientierten Entwurf?
Und hier steht entweder Klasse oder Paket.
Ich tendiere eher dazu zu sagen, die Klasse ist eher so das, was
analog dazu ist.
Natürlich kann man auch mehrere Klassen zu einem Modul zusammenfassen,
aber in vielen Programmiersprachen wird es dann schwierig mit
Schnittstellen.
Spätestens wenn wir uns das dann überlegen wollen mit den Modul
-Schnittstellen, dann sind wir doch wieder eher auf der Klassen-Ebene,
wenn wir das mit Modulen vergleichen wollen.
Und insbesondere hatten wir ja auch das letzte Mal gesehen, dass wir
ja Module relativ klein schneiden wollen.
Das passt dann mit Paketen als Abstraktionen auf der
objektorientierten Seite auch nicht so ganz zusammen.
Deshalb würde ich eher von Klasse sprechen, wenn ich jetzt ein
analoges Konzept zum Modul bauen wollen würde.
Die Pakete in objektorientierten Entwürfen fassen dann mehrere Klassen
zusammen und kapseln wiederum gemeinsame Entwurfsentscheidungen.
Auch das kennen Sie sicherlich schon, wenn Sie in Java programmiert
haben bereits.
Auch da versucht man natürlich semantisch zusammenhängende Klassen und
Schnittstellen in gemeinsamen Paketen zusammenzufassen, damit man da
einfach eine bessere Gliederung erzeugt und sofort sehen kann, was zu
was gehört.
Was haben wir noch?
Wir haben anstelle des Modulführers im modularen Entwurf jetzt Paket-
und Klassenführer.
Auch wieder hier der Hinweis, am besten mit UML-Klassen und Paket
-Diagrammen in UML.
Und auch hier wird wieder die jeweilige Entwurfsentscheidung
dokumentiert.
Und hier ist jetzt noch der Hinweis, genau mit den Modul
-Schnittstellen, wo ich gerade eben drauf hinaus wollte, das sind
jetzt eben die Klassen-Schnittstellen.
Oder eben abstrakte Klassen, je nachdem.
Im internen Entwurf sieht es dann so aus, dass wir in die Pakete
reinschauen, Benutzrelationen auf Paketebene aufbauen.
Das würde aber auch nicht unbedingt immer reichen.
Auch da würde ich so ein bisschen auf der Tonspur mal dagegen
sprechen.
Es reicht nicht, unbedingt die Benutzrelation auf Paketebene
aufzubauen.
Das muss man teilweise auch mal genauer anschauen.
Auch mal in die Pakete reinschauen, da Benutzrelationen aufbauen.
Vielleicht machen wir das auch mal auf einem Übungsblatt.
Wäre doch ganz nett.
Mal schauen.
Auch hier wieder der Hinweis mit dem Feinentwurf.
Wenn nötig, muss man den halt machen.
Aber das ist tatsächlich genau das gleiche wie im modularen Entwurf.
So.
Jetzt ist es aber so, dass wir ein bisschen mehr tun können im
objektorientierten Entwurf, ganz einfach, weil uns die
Objektorientierung Dinge liefert, wie Mehrfachinstanzierung von
Klassen, Beerbung und vor allem Polymorphie.
Oder aber auch Variantenbildung, indem wir Mehrfachimplementierung
einer Schnittstelle haben können.
Und das klingt jetzt irgendwie gar nicht so viel mehr und irgendwie
ziemlich detailliert.
Nur so kleine Verbesserungen irgendwo im Speziellen.
Aber das führt tatsächlich dazu, dass man jetzt mit dem
objektorientierten Entwurf sehr viel mehr umsetzen kann oder sehr viel
mehr schöne Dinge bauen kann als mit dem modularen Entwurf.
Und insbesondere haben sich dort die Entwurfsmuster herausgebildet,
die wir uns dann anschauen werden.
Wahrscheinlich erst am Montag.
Und genauso auch Architekturmuster.
Die gehen häufig auch sehr gut im modularen Entwurf, aber auch die
Architekturstile sind eher mit objektorientiertem Entwurf zu Hause.
Das heißt, auch da wird es einfach spannender, wenn wir die
Objektorientierung mit dabei haben.
Hier auch nochmal der Hinweis, es gibt natürlich auch Entwurfsmuster,
die auch im modularen Entwurf funktionieren.
Das muss man dann jeweils sehen, wenn man das Entwurfsmuster an der
Hand hat, ob man das dann jetzt auch umsetzen könnte in einer
modularen Programmiersprache.
Gut.
Das war es tatsächlich schon zum objektorientierten Entwurf.
Wünscht sich jemand mehr?
Hat jemand eine Frage dazu?
Ansonsten, wie versprochen, denken Sie einfach an den modularen
Entwurf.
All das, was dort erklärt wurde, hat Gültigkeit.
Keine Anmerkungen wünsche?
Wunderbar.
Dann noch ein kurzer Hinweis, wenn Sie mal sich näher mit
objektorientiertem Entwurf auseinandersetzen, dann wird Ihnen früher
oder später der Herr Bootsch, der Grady Bootsch, über den Weg laufen.
Er hat ja sehr viel Pionierarbeit geleistet.
Wenn man sich mal ein bisschen näher damit beschäftigen möchte,
empfehlen wir diese Veröffentlichung.
Dort gelistet sind, also das ist ein großer Pionier, taucht eigentlich
immer in dem Zusammenhang wieder auf, der Name.
Nur, dass Sie das mal gesehen haben.
Oder sich, wenn Sie möchten, näher damit beschäftigen können.
Gut.
Dann sind wir an der Stelle tatsächlich durch mit dem Grundlagen zum
Entwurf und machen jetzt schon weiter mit den Architekturstilen.
Die Architekturstile.
Hier geht es darum, gewisse Strukturen von üblichen Software
-Architekturen mal anzuschauen und vor allem auch zu sehen, dass sowas
vielleicht übertragbar sein kann.
Gewisse Strukturen, auch im Großen, lassen sich wunderbar übertragen,
wenn man eigene Dinge baut.
Es gibt einfach fertige Strukturen, zum Beispiel für Betriebssysteme.
Wird wahrscheinlich keiner von Ihnen ein ganzes Betriebssystem bauen,
aber zu wissen, wie es aufgebaut ist, ist doch sehr hilfreich, wenn
man zum Beispiel einzelne Bestandteile davon nachbauen möchte oder was
hinzufügen möchte.
Und deshalb fangen wir genau an der Stelle mal an mit dem Aufbau von
großen Software-Architekturen und fangen an mit so einem, würde ich
schon fast sagen, Meta-Architekturstil, nämlich mit der virtuellen
Maschine.
Wer hat schon mal was von virtuellen Maschinen gehört?
Ja, wunderbar.
Das sind fast alle.
Da kann ich ja jetzt aufhören.
Nächste Folie.
Nein, wir schauen natürlich kurz drauf.
Also, was ist eine virtuelle Maschine?
Das ist erstmal eine Menge von Software-Fehlern und Objekten, die auf
einer darunterliegenden, abstrakten oder realen Maschine aufbauen und
diese ganz oder teilweise verdecken können.
So allgemein ist tatsächlich die Definition und dann muss man sich
natürlich mal überlegen, was könnten jetzt so mögliche virtuelle
Maschinen sein?
Da kennt man natürlich schon einige.
Der Klassiker, mit dem Sie auf jeden Fall schon zu tun hatten, ist die
Java -VM oder natürlich jedes Betriebssystem.
Und was ganz wichtig ist für virtuelle Maschinen vorneweg, dort ist
die Benutzrelation immer hierarchisch.
Also wenn da Zyklen auftreten, dann haben wir ein Problem.
Dann funktioniert irgendwas nicht.
Und das kommt genau aus dieser Forderung mit der Verdeckung.
Wir müssen die darunterliegende Maschine verdecken.
Wir wollen eine Abstraktion haben unserer tatsächlich konkreten
Maschine.
Denken Sie mal an Betriebssysteme, da ist das besonders eklatant, wenn
Sie einfach nur Ihren Rechner starten, ohne ein Betriebssystem, dann
können Sie nur die Maschinenbefehle ausführen und dann können Sie,
sind wir mal ehrlich, eigentlich gar nichts tun mit dem Rechner.
Und das Betriebssystem stellt Ihnen dann die grundlegende
Funktionalität zur Verfügung und damit können Sie dann arbeiten in
eine Abstraktion der eigentlichen Maschine.
Ähnlich ist es bei der JVM, schauen wir auch gleich nochmal im Detail
rein.
Die gibt Ihnen eine Abstraktion Ihres Betriebssystems samt
darunterliegender Architektur, sodass eigentlich die JVM für Sie als
Java -Programmierer als Draufsicht immer gleich aussieht.
Egal wo Sie die laufen lassen, die abstrahiert immer das
Betriebssystem der zugehörigen Maschine und Sie können einfach Java
programmieren, egal für welches Zielsystem, so zumindest die Theorie.
Gut, schauen wir mal ein bisschen im Detail rein.
So eine abstrakte Maschine wird in der Regel von mehreren Modulen oder
Paketen implementiert, weil es ist einfach groß genug.
Und dann ist es wichtig, die jeweiligen Befehle in Schnittstellen
bereitzustellen und die darunterliegende Maschine wird teilweise oder
ganz verdeckt.
Was bedeutet das jetzt?
Die Befehle, die auf der eigentlichen Maschine zur Verfügung stehen,
an die kommen wir jetzt eigentlich nicht mehr ran.
Das ist der entscheidende Punkt.
Und das passiert entweder ganz oder teilweise.
Bei der JVM funktioniert das vollständig.
Sie können eigentlich nicht auf irgendwelche Betriebssysteme Befehle
zugreifen, sondern eben nur auf die, die Ihnen die JVM zur Verfügung
stellt.
Bei Betriebssystemen an sich ist das häufig so, dass nur ein Teil
verdeckt wird, dass Sie auf einige Maschinenbefehle noch zugreifen
können.
Also da haben wir dann eher eine Teilverdeckung.
Und das Problem ist halt, wenn wir nur Teilverdeckung haben, dann
müssen wir sehen, dass wir dort Konsistenz haben.
Wenn wir komplett verdecken, ist das einfach, dann wissen wir, wir
haben hier ein Befehl, was auch immer das ist, ein Print zum Beispiel
und der wird dann vom Betriebssystem umgesetzt, dann wiederum vom
eigentlichen System umgesetzt.
Und wir wissen, wie diese Umsetzung funktioniert und wir wissen auch,
was passiert ist.
Anders sieht das aus, wenn wir nur teilweise verdecken und im Grunde
an dieser Virtualisierung vorbei irgendwelche Befehle ausführen, dann
weiß die Virtualisierung natürlich nicht, dass das passiert ist.
Und dann müssen wir sehen, dass wir das wieder aufbauen.
Wie sieht das jetzt aus mit der Verbreitung?
Wir wollen das natürlich so bauen, dass diese abstrakte Maschine so
gebaut ist, dass sie von möglichst vielen Programmen verwendet werden
kann.
Denken Sie wieder an die JVM, wir können alles in Java programmieren
und auf der JVM ausführen.
Und deshalb sind eben abstrakte Maschinen häufig als eine Art API
verwirklicht.
Und jetzt schauen wir nochmal so ein paar konkrete Beispiele rein.
Betriebssysteme, da haben wir eben eine sehr mächtige Abstraktion
einer konkreten Maschine und dort haben wir mehrere
Abstraktionsschichten sogar, die werden wir uns dann später auch
nochmal in anderen Architektur-Studien anschauen, nämlich in der
Schichtenarchitektur, die ist nämlich auch umgesetzt im Betriebssystem
üblicherweise, denn dort haben wir einmal Prozessverwaltung, dann
darüber die virtuelle Speicherverwaltung, Datenhaltung, Kommunikation
und dann eben Kommandosprachen bzw.
grafische Benutzeroberflächen.
Und das Betriebssystem an sich verdeckt privilegierte Befehle, die für
die Implementierung von Betriebssystemdiensten nötig sind und dass wir
die nicht benutzen, das wird eben zur Laufzeit überprüft und das führt
entweder dann zu einer Unterbrechung, wenn wir irgendwas
Privilegiertes ausführen wollen auf unserer Maschine, was wir nicht
dürfen, dann meckert das Betriebssystem oder das wird ignoriert, je
nachdem.
Gut.
Fragen bisher her?
Das kommt auch alles nochmal ganz genau in der
Betriebssystemvorlesung, deshalb mache ich hier mal ein bisschen
flotter weiter.
Schauen wir uns lieber nochmal die JVM an, die hier nur ganz kurz
tatsächlich dasteht.
Die interpretiert an sich Bytecode, der vom Java-Übersetzer kommt und
jetzt sehen wir schon, jetzt wird es wieder noch ein Tick komplexer.
Wir können jetzt auch den Java-Übersetzer wiederum als abstrakte
Maschine interpretieren und die wird in Java programmiert, stellt den
Bytecode zur Verfügung und der wird dann interpretiert von der
virtuellen Maschine JVM und so weiter und so weiter.
Also eigentlich hat man häufig eine Reihe von abstrakten Maschinen
übereinander.
Was haben wir noch für Beispiele von abstrakten Maschinen?
Ein System zur Bearbeitung elektronischer Post könnte man auch
interpretieren als abstrakte Maschine.
Also da könnte man sowas anbieten wie Empfangen und Senden von
Nachrichten und darunter liegende Protokolle, die man eigentlich dafür
braucht, werden verdeckt.
Man zeigt also nach außen hin dem Nutzer nur die Möglichkeit,
Nachrichten zu senden, Nachrichten zu empfangen und darunter passiert
eigentlich ganz viel.
Aber das ist alles verdeckt für den Nutzer, denn das braucht der nicht
wissen.
Oder wenn wir das als API anbieten, dann muss man das auch nicht
wissen.
Dass man da viele Dinge noch tun muss, damit tatsächlich das Senden
und Empfangen funktioniert.
Und immer wenn wir im Grunde so eine Verdeckung haben und eine
Abstraktion, dann kann man von einer abstrakten Maschine sprechen.
Bei Makros ist das ähnlich.
Überspringen wir das mal.
Gehen wir lieber weiter auf die Virtualisierung bei Dienstgebern im
Internet.
Und das ist jetzt was, was Ihnen bestimmt bekannt vorkommt.
Das ist wahrscheinlich das, worauf sich viele gemeldet haben bei
virtuellen Maschinen.
Das kenne ich schon irgendwie.
Großrechner werden häufig virtualisiert.
Das heißt, wir haben einen echten physischen Großrechner und jede
Menge virtuelle Maschinen, die darauf laufen.
Und dann kann ein Nutzer ein beliebiges Betriebssystem installieren.
Natürlich ist das, was Sie vielleicht auf Ihrem Laptop oder auf Ihrem
PC machen, ähnlich.
Wenn Sie dort virtuelle Maschinen installieren.
Das funktioniert nach dem gleichen Prinzip.
Und das Schöne ist, wenn man sowas macht, so eine virtuelle Maschine,
die weiß ja nicht so furchtbar viel von dem, was darunter liegt.
Die muss natürlich wissen, wie das System darunter funktioniert.
Aber die ist ja für sich erstmal abgeschlossen.
Das heißt, man kann dann wunderbar zum Beispiel von IBM Großrechner,
kann man von einem auf den anderen portieren.
Wenn da irgendwie ein Knoten ausfällt im Rechenzentrum von IBM, dann
darf der Kunde von IBM natürlich nichts davon mitbekommen.
Das heißt, dann wird die virtuelle Maschine einfach umgezogen bei
einem anderen Knoten.
Und schon ist das ganze Problem gelöst.
Das heißt, das sieht man dann einfach gar nicht.
Da kann man auch dann Ausfallsicherheit sowas wunderbar mit umsetzen.
Oder eben auch Lastverteilung.
Das ist auch sehr schön, wenn man zum Beispiel als Anbieter von so
Großrechenanlagen sagt, wir haben 15 von diesen Großrechnern und 23
VMs gerade von Kunden.
Völlig egal, wie viele das sind.
Und dann laufen gerade nur 4.
Dann kann ich das vielleicht mit einem großen Rechenknoten erledigen.
Und wenn ich dann noch 5 habe, die jetzt irgendwas rechnen wollen,
dann muss ich halt einen zusätzlichen anschalten.
Aber ich spare mir natürlich einen Haufen Geld, wenn ich das nicht
machen muss.
Wenn ich dann einfach die Last sinnvoll verteilen kann.
Und dann gibt es vielleicht eine virtuelle Maschine, die ist die ganze
Zeit nur idle.
Dann passt die vielleicht noch irgendwo als fünfte Virtualisierung mit
auf den einen Rechenknoten.
Und erst wenn sie was tut und plötzlich mehr Ressourcen verbraucht,
dann schieben wir sie mal auf einen anderen Knoten.
Gut.
Wunderbar.
Soweit verstanden?
Virtuelle Maschinen.
Wenn Sie das mal gesehen haben.
Wunderbar.
Dann noch ein anderes größeres, ein größerer Architekturstil.
Die Programmfamilie.
Wer hat das schon mal gehört?
Programmfamilie.
Oder Softwareproduktlinie.
Das sind aber wenige.
War keiner in der zweiten Vorlesung?
Da haben wir das schon mal ganz kurz angesprochen, als es um SVN ging.
Beziehungsweise um Variantenbildung.
Da hatten wir schon mal gesagt, wenn wir jetzt mehrere Varianten haben
wollen, dann legen wir die halt nebeneinander und haben dann
unterschiedliche Produktlinien gegebenenfalls.
Und was ist jetzt erstmal eine Produktlinie?
Vielleicht fangen wir mal mit der Definition an.
Das wäre eine Menge von Programmen, die erhebliche Anteile von
Anforderungen, Entwurfsbestandteilen oder Softwarekomponenten
gemeinsam haben.
Und die Idee oder die Hoffnung ist, dass man jetzt eben neue
Mitglieder dieser Familie schnell entwickeln kann.
Denken Sie vielleicht mal wieder an Autobauer.
Die haben auch häufig Produktlinien.
Da hat man vielleicht eine Grundkarosse, die man nur minimal abändern
muss und hat dann neue Modelle.
Und das ist genau die Idee.
Man hat einfach so einen Grundbaukasten, den man immer wieder
verwenden kann.
Und kann dann einfach nur durch kleine Abänderungen wunderbar neue
Modelle erzeugen.
Und so ähnlich funktioniert das in Software auch.
Denken Sie mal an sowas wie Home Edition, Enterprise Edition oder
sowas.
Das ist häufig einfach nur genau das, nämlich ein Mitglied einer
Produktfamilie.
Die haben häufig im Kern sehr viel gemein und werden dann eben nur zum
Beispiel in der Funktionalität abgewandelt.
Und die Hoffnung, die so ein bisschen dahinter steckt und sich
natürlich auch bewahrheitet, bei den Autobauern sieht man das ein
bisschen deutlicher als vielleicht jetzt bei Software.
Aber auch da ist es natürlich klar, dass wenn wir so ein neues
Mitglied bauen können und den Großteil einfach so lassen können,
unsere Software, wie sie ist, dann sind wir natürlich deutlich
kostengünstiger, als wenn wir das Ganze nochmal von vorne bauen
müssten.
Und das macht man eben, indem man die Gemeinsamkeiten, die die
einzelnen Softwareausprägungen dort haben, nutzen.
Und kann dann wunderbar Entwürfe wiederverwenden, Spezifikationen und
Anforderungen, Softwarekomponenten, ganze Bibliotheken im besten Fall
einfach wiederverwenden.
Und das spart natürlich sehr viel Zeit und Geld.
Klassiker bei der Variation, habe ich schon gesagt, sind
Funktionalität und benutzte Plattformen offensichtlich.
Also wenn Sie eine Anwendung bauen und das mal nicht für Java machen,
sondern C oder C++, eine Variante bauen für Windows-Betriebssysteme
und eine für Linux-Betriebssysteme, dann müssen Sie sich eben auch
über die Variantenbildung Gedanken machen.
Aber ein Großteil Ihrer Software bleibt dann hoffentlich gleich,
vorausgesetzt Sie haben vorher den Entwurf richtig gebaut.
Dann ist die Variantenbildung an der Stelle einfach.
Wie können sich jetzt die Mitglieder der Programmfamilie
unterscheiden?
Das hatte ich schon gesagt, Hardware- oder Betriebssystem
-Konfigurationen können sich unterscheiden oder Format der Ein- und
Ausgabe oder eben Funktionsumfang.
Je nachdem wie viel man bezahlt.
Häufig gibt es ja auch so Freeware-Varianten und dann, wenn man die
ganze Funktionalität haben will, dann muss man halt zahlen und dann
werden eben zusätzliche Module hinzugeladen.
Wie sieht es intern aus?
Auch da gibt es natürlich die Variante.
Es geht ja nicht immer nur darum, mehr Geld zu verdienen.
Es geht ja manchmal auch darum, einfach Dinge anders zu lösen.
Man kann ja sich auch sowas überlegen wie man löst das Problem einmal
mit der einen Datenstruktur oder mit dem einen Algorithmus und dann
möchte man das austauschen, austauschbar machen können und das mit
einem anderen Algorithmus lösen.
Und das kann natürlich manchmal sinnvoll sein oder nötig sogar.
Jetzt noch ein anderer Punkt, den man unterscheiden muss, wenn man so
eine Produktlinie vor sich hat, nämlich zwischen allgemein und
flexibel.
Wer kann sich vorstellen, was der Unterschied ist?
Vielleicht mit einem Beispiel?
Das ist mal eine Idee.
Also hier steht es ja schon.
Produkt ist allgemein, falls wir es in vielen Situationen benutzen
können.
Wer kennt so Beispiele?
Keiner?
Keiner schon mal irgendwie Software installiert auf seinem Rechner?
Ja.
So ein Webbrowser genau.
So ein Webbrowser kann man fast immer installieren und egal auf
welcher Plattform, sehr schön, vielen Dank.
Das bedeutet aber häufig auch und das steht auch schon dort, dass wir
diese Allgemeinheit halt dadurch erkaufen, dass wir sehr viele Module
haben, die vielleicht gar nicht benutzt werden auf unserem konkreten
System.
Das heißt, wir haben da einfach sehr viel Variantenbildung schon in
diesem eingekauften oder kostenlosen Produkt mit drin.
Dann kann wir das natürlich immer wunderbar benutzen.
Egal wo, ist aber häufig einmal Platzproblem, dass wir sehr viel
Speicherplatz dadurch verschwenden eigentlich und häufig natürlich
auch ein Laufzeitproblem, dass wir eigentliche Optimierungen für ein
gewisses System gar nicht nutzen können, weil wir es halt
allgemeingültig gebaut haben.
Und die Umkehrung ist dann die Flexibilität.
Dort bauen wir dann das Programm so, dass das eben leicht abänderbar
ist für konkrete Zielsysteme.
Also auch dort möglichst viel im Kern gemeinsam, aber wir liefern eben
nicht alle Varianten gleich mit aus, sondern sagen, je nachdem, wo du
es haben möchtest, nimm halt das Modul oder das Modul oder das Modul
mit dazu.
Genau so oder so wollen wir es natürlich so haben, dass ein System
möglichst flexibel eingesetzt werden kann, Wartung möglichst
vereinfacht wird, dass es möglichst überall läuft.
Und das funktioniert halt nur mit einem möglichst flexiblen Entwurf.
Das ist nochmal so ein bisschen die nachträgliche Motivation für das,
was wir am Montag gesehen haben zum Thema Wie baut man denn eigentlich
richtig den Entwurf auf?
Denken Sie nochmal dran an die Module und wo man überall Module bilden
sollte.
Zum Beispiel eben auch wenn es darum geht, auf welchem System soll das
laufen.
Alles was speziell für ein Betriebssystem entwickelt wird an Code,
sollte eben in eigenen Module ausgelagert werden.
Das ist nochmal so der Hinweis.
Dann wären wir mit diesen großen Stilen fertig und schauen wir jetzt
mal ein bisschen in die kleinteiligeren rein.
Es gibt noch Fragen bis hierher?
Alle noch im Ausschlafen vom Feiertag, wunderbar.
Dann schauen wir jetzt die konkreten Architekturstile an und da haben
wir Ihnen eine kleine Ausnahme mitgebracht und zwar die
Schichtenarchitektur, Klient, Dienstgeber, Partnernetze, Datenablage,
Modell, Präsentation, Steuerung oder Model, View, Controller,
vielleicht ist das eher geläufig, dann das Fließband oder auch
Pipeline, eine Rahmenarchitektur oder Framework genannt und zum
Schluss noch die dienstorientierte Architektur Service-Oriented
Architecture und die gehen wir jetzt einfach mal nacheinander durch
und schauen uns an was die jeweils so können.
Und fangen an mit der Schichtenarchitektur, auch bekannt als der
Klassiker.
Es gibt ganz viele Schichtenarchitekturen die Sie so umgeben, wenn Sie
gerade Ihren Laptop offen haben, dann haben Sie dort ein
Betriebssystem, was sicherlich als Schichtenarchitektur gebaut ist und
auch so verwendet man das auch ziemlich häufig und wir werden auch
gleich sehen, warum das so ist.
Erstmal wieder eine Definition, die dieses Mal recht länglich ist.
Eine Schichtenarchitektur ist die Gliederung einer Softwarearchitektur
in hierarchisch geordnete Schichten, so einfach sagt ja der Name
schon.
Wichtig ist hier, dass hierarchisch eine Schicht besteht aus einer
Menge von Softwarekomponenten, Module, Klassen, Objekte, Pakete mit
einer wohl definierten Schnittstelle, nutzt die darunter liegenden
Schichten und stellt seine Dienste darüber liegenden Schichten zur
Verfügung.
Zwischen den einzelnen Schichten ist die Benutzrelation linear, auch
wichtig.
Raumartig oder ein a-zyklischer Graph.
Innerhalb einer Schicht ist die Benutzrelation beliebig.
Und so sieht das dann konkret aus.
Schichten hier angedeutet in blau, einzelne Komponenten weiß
dazwischen.
Und wichtig ist hier, wenn wir uns diese dreischichtige Architektur
anschauen, wie die Kommunikation aussieht.
Heißt also, wie die Benutzrelation hier aussieht.
Wer benutzt hier wen?
Wir sehen pro Schicht ist uns das völlig egal.
Benutz doch wen du willst.
Komponente 2, 1 benutzt 2, 2 und umgekehrt 2, 3 benutzt 2, 2.
Hier haben wir auch einen Zyklus drin.
Das gleiche könnte auch in den darunter liegenden Schichten so sein.
Aber wichtig ist, zwischen den Schichten haben wir eine
Benutzrelation, die streng hierarchisch ist.
Also eine oben liegende Schicht benutzt immer nur die darunter und
nicht umgekehrt.
Kann natürlich sein, dass zum Beispiel Schicht 1 Schicht 3 hier unten
benutzt.
Das hängt aber davon ab wie strikt wir unsere Schichtenarchitektur
interpretieren.
Das sehen wir auch gleich nochmal diese Unterscheidung.
Hier steht es nochmal zusammengefasst für Sie eine Schicht ist also
immer eine Art Subsystem, welches anderen Schichten Dienste zur
Verfügung stellt.
Dabei gibt es folgende Einschränkungen.
Nämlich, dass eine Schicht nur Dienste von niedrigeren Schichten
überhaupt benutzen darf.
Und eben keine höheren.
Das ist ganz wichtig.
Horizontal kann man mehrere Subsysteme bauen.
Man kann auch mehrere Partitionen bauen.
Auch das sehen wir gleich nochmal.
Und damit kann man zum Beispiel bessere Austauschbarkeit oder einzelne
Aufteilungen unserer Systeme besser erreichen.
Jetzt noch die Unterscheidung.
Ich hatte es schon angedeutet ob wir denn jetzt an Schichten quasi
vorbeigreifen dürfen oder nicht.
Da gibt es einmal die intransparente Schichtenarchitektur.
Und dort, das können Sie sich so richtig vorstellen, werden die
Schichten einfach undurchsichtig.
Genau da darf ich das eben nicht.
Also stellen wir uns mal vor, wir haben hier diese drei Schichten A, B
und C.
Dann darf A auf B zugreifen und B auf C.
Es ist aber nicht erlaubt, dass eben A vorbei an B auf C zugreift.
Eben weil B als Schicht intransparent ist.
Und natürlich gilt, in die andere Richtung geht ja mal gar nicht.
Das ist immer noch unsere hierarchische Schichtenarchitektur.
Also das ist alles nicht erlaubt.
Anders ist es bei der transparenten Schichtenarchitektur.
Dort ist also zum einen das erlaubt, wie gerade eben auch, aber auch
dieser Zugriff von A direkt nach C.
Also da darf man dran vorbeigreifen.
Auch hier gilt natürlich, nach oben greifen blöd.
So, wer kann sich denn vorstellen, was die Vor- und Nachteile jeweils
sind?
Hat man eine Idee?
Ist jetzt das eine richtig oder das andere?
Okay, also ihr Kommilitone sagt, wenn wir das Ganze transparent bauen,
also so wie wir es hier gerade haben, dann kann das schneller sein,
effizienter, weil wir eben von A direkt auf C zugreifen können.
Warum ist das schneller?
Okay, also ihr Kommilitone sagt das ganz richtig.
Manchmal möchte man ja einfach nur einen kleinen Ausschnitt von C
ansprechen.
Eine primitive Operation.
Und das kann man dann einfach direkt machen, ohne sich hier eben durch
Schicht B durchzuwühlen und da noch einen Delegationsschritt zu haben,
der natürlich auch wieder Laufzeit verbraucht.
Genauso, wenn wir natürlich noch mehr Schichten hätten, wird das ganze
Problem natürlich noch größer.
Hier ist es noch relativ übersichtlich, aber häufig hat man sehr viel
mehr Schichten und dann ist das natürlich ein Problem.
Was ist ein Nachteil von dieser transparenten Variante?
Okay, also ihr Kommilitone geht schon mindestens zwei Schritte weiter,
als ich an dieser Stelle wollte, aber sehr richtig.
Er meint, man kann jetzt einfach Schicht C nicht mehr austauschen,
weil man dann auch Code in Schicht A ändern müsste, nämlich der Teil,
der auf C zugreift.
Und ein großer Vorteil der Schichtenarchitektur, den wir gleich sehen
werden, ist eben genau diese Austauschbarkeit von Schichten und die
nehmen wir uns dann ein Stück weg.
Genau, was haben wir noch?
Noch eine Idee, was auch noch ein Nachteil sein könnte?
Vielleicht ein bisschen zu offensichtlich, vielleicht ist er auch gar
nicht da.
Noch jemand eine Idee?
Wir nehmen uns genau diesen Delegationsschritt, den wir eigentlich
durch diese hierarchische Schichtung aufgebaut haben, nehmen wir uns
ja dann weg.
Vielleicht wollten wir ja das genauso machen, dass eben B allein nicht
Zugriff auf C hat.
Dass nur B weiß, wie C aussieht und eben A das gar nicht wissen soll.
Und das nehmen wir uns dann natürlich auch weg.
Da kann es dann halt sein, dass Dinge passieren, die wir eigentlich
nicht vorgesehen haben.
Gut, dann schauen wir doch mal rein jetzt in die Vorteile der
Schichtenarchitektur, was Ihnen auch so ein bisschen beantwortet,
warum die so gerne eingesetzt wird.
Wir haben zum einen eine übersichtliche Strukturierung in
Abstraktionsebenen und das kommt uns Menschen sehr entgegen, denn so
funktionieren unsere Gehirne, so denken wir ganz gerne.
Wir machen Dinge gerne abstrakter und bauen gedanklich solche
Schichten auf.
Das heißt, das unterstützt so ein bisschen unseren Denkprozess.
Wunderbar.
Und das nächste, das kommt auch sehr vielen Leuten entgegen, die
Einschränkungen, die hier getroffen werden, die sind nicht so
besonders strikt.
Also was in den Schichten passiert, ist prinzipiell erstmal egal.
Wir haben nur diese Forderung nach der strengen Hierarchie zwischen
den Schichten.
Aber was in den Schichten passiert, naja, das ist dem Entwerfer
überlassen oder dem Implementierenden.
Und das kommt natürlich auch vielen sehr entgegen, die gerne Chaos
implementieren.
Muss man aber natürlich auch aufpassen, dass dann nicht zu viel Chaos
passiert.
So oder so wird Wiederverwendbarkeit und Änderbarkeit und Wartbarkeit
sowie Portabilität und Testbarkeit schon sehr gut unterstützt, einfach
wenn man pro Schicht denkt.
Also die einzelnen Schichten, wir Kommilitonen haben es wunderbar
gesagt, kann man austauschen.
Und man kann auch wunderbar testen.
Dadurch, dass man diese hierarchische Struktur hat, kann man wunderbar
einfach anfangen, das System von unten nach oben aufzubauen.
Wunderbar.
So, hier sind nochmal die Vor- und Nachteile jeweils der
intransparenten Schicht.
So aus der Sichtweise ist das jetzt gebaut.
So hatten wir es ja auch.
Dann kann es natürlich diese Effizienzverluste geben, das hatten wir
ja schon gesagt, wenn wir eben durch mehrere Schichten durch müssen
zunächst mit unserem Aufruf.
Aber auf der anderen Seite haben wir eben diese klare Abgrenzung nicht
mehr.
Und man muss dann sehr auch aufpassen, das hatte ich ja gerade eben
schon gesagt, dass eben pro Schicht nicht zu viel Chaos herrscht.
Das ist ganz wichtig, dass man eben nicht sagt, okay, wir haben ja
unsere Schichten gebaut, aber was da drin passiert, ist völlig egal.
Deshalb hatte ich vorhin auch schon gesagt, diese Betrachtung der
Benutztrelationen nur auf Paketebene ist häufig nicht ausreichend.
Also auch auf Klassenebene oder wie das hier genannte Komponentenebene
bei der Schichtenarchitektur muss man dann ebenfalls auch machen.
Sonst baut man sich da Zyklen, die dann auch nicht mehr funktionieren.
Was gibt es so für übliche Schichtenarchitekturen?
Die drei Schichtenarchitektur haben wir jetzt schon gesehen.
Klassische Beispiele sind da hier Benutzerschnittstellen,
Anwendungskern und ein Datenbanksystem.
Sowas baut man häufig als Schichtenarchitektur.
Oder aber auch sowas wie eine dreistufige Architektur nennt man das
dann häufig, wenn es darum geht, dass man die Stufen auf
unterschiedliche physikalische Systeme auslagert.
Das ist natürlich auch ein großer Vorteil dieser Schichtenarchitektur.
Man kann einfach sagen, Schicht 1, die Benutzerschicht, läuft auf dem
Klienten und die anderen beiden Schichten laufen auf einem
Dienstgeber, also einen Anwendungskern und eine Datenbank.
Denken Sie mal an unser Beispiel aus der Übung.
Da haben wir ja auch unser iMatch und da ist es ja auch so, dass wir
die Applikation auf dem Smartphone laufen lassen wollen und den Rest
der läuft eben auf dem Server der PeerCorp und das wäre auch genau so
eine dreistufige Architektur wo die eine Hälfte oder das eine Drittel
beim Nutzer läuft und der Rest dann eben auf dem Server.
Man kann das auch noch ein bisschen komplexer bauen, sowas hier geht
natürlich auch.
Jetzt haben wir hier so eine teiltransparente Schicht mit dabei und
mehrere darüber liegende Schichten für zum Beispiel ein einfaches
Betriebssystem, könnte man sich hier vorstellen.
Man hat ein Betriebssystemkern, man hat Grundfunktionen, die
bereitgestellt werden und darüber hat man dann Anwendungen, die vom
Nutzer verwendet werden sollen und dann hat man Anwendungskern 1 für
die eine Anwendung und eine Benutzerschnittstelle und dann hat man
eine Anwendung 2 mit einer zweiten Benutzerschnittstelle und
offensichtlich kann man hier wunderbar beiden Sachen austauschen.
Man kann hier einfach was Neues hinsetzen ohne dass hier unten
irgendwas passiert, weil wir eben keinen Zugriff in die Richtung
haben.
Der Grundfunktion benutzt irgendwas aus diesem Anwendungskern.
Das ist ja völliger Quatsch.
Das heißt, wir können da wunderbar noch eine dritte, vierte Anwendung
daneben legen.
Wir können die Anwendung hier wieder wegnehmen.
Wenn Sie sich da nicht an der Schichtenarchitektur halten würden, dann
hätten Sie wunderbares Chaos auf Ihrem Rechner.
Gut.
Das ist noch ein Beispiel.
Da wollen wir jetzt glaube ich auch nicht nochmal drüber gehen.
Schauen wir lieber nochmal ganz kurz in das Betriebssystem rein.
Das hatte ich ja schon auch mal angedeutet.
Betriebssysteme sind üblicherweise in Schichten gebaut.
Und das ist üblicherweise sowas wie Prozessverwaltung,
Speicherverwaltung, Dateiverwaltung, dann Kommunikation,
Netzwerkschnittstellen, zum Beispiel darüber Kommandoschnittstellen
oder grafische Benutzeroberflächen.
Die liegen meistens der Hierarchie eher nebeneinander und dann eben
die Anwendung oben drüber.
Oder sowas wie Datenkernübertragung.
Da hat man auch häufig mehrere Schichten oder Informationssysteme, die
auf Datenbanken aufbauen.
Da ist eben der Datenbankzugriff häufig eine Schicht davon.
Also sowas könnte man sich vorstellen, alles als Schichtenarchitektur.
Gut.
Gibt es Fragen bisher zu Schichtenarchitektur?
Keine Fragen.
Wunderbar.
Dann schauen wir mal noch ein bisschen tiefer rein in die Schichten.
Weil was wir uns noch nicht überlegt haben so richtig ist jetzt
eigentlich, wie funktioniert denn eigentlich der Zugriff zwischen den
Schichten?
Wir haben immer gesagt, der geht irgendwie von oben nach unten.
Die Komponenten aus Schicht A dürfen ausschließlich auf die
Komponenten aus Schicht B zugreifen.
Jedenfalls bei intransparenten Schichtenarchitekturen.
Aber je nachdem wie groß unser System ist und wie viele Komponenten
unsere Schichtenarchitektur jetzt enthält, kann auch das natürlich
immer noch zu ziemlich viel Chaos führen.
Wir müssen ja dann immer noch überlegen, auf welche Schnittstelle,
welcher Komponente in der darunterliegenden Schicht wollen wir denn
jetzt eigentlich zugreifen.
Und deshalb kombiniert man häufig diese Schichtenarchitektur mit dem
Entwurfsmuster Fassade, was wir uns dann wahrscheinlich am Montag
anschauen werden.
Und eine Fassade kann man sich im Grunde vorstellen wie eine Tür zu
einer Schicht.
Das baut man so als die Tür, in die man in die Schicht rein darf und
von dort aus wird man im Grunde weitergeleitet.
Da steht dann der Türsteher und sagt, Sie wollen zur Komponente XY,
bitte da entlang.
So funktioniert das im Wesentlichen.
Wir haben dort also in der Fassade mehrere Klassen, die dort nochmal
abstrahiert werden.
Das heißt, wir delegieren dann jeweils dorthin, wo man eben hin
möchte.
Das sieht man vielleicht besser am Bild.
Gerade eben hatten wir ja dieses Übersichtsbild.
Also stellen Sie sich mal vor, das hier ist eine Schicht und das
darüber ist eigentlich auch nochmal eine Schicht.
Da fehlt jetzt der Kasten drum.
Hier drüben das gleiche.
Und was wir bisher hatten, war eben der Zugriff aller Komponenten aus
Schicht A direkt auf die Komponenten in Schicht B.
Und das kann eben mitunter komplex sein und verringert natürlich auch
wiederum die Austauschbarkeit hier unten.
Wenn wir jetzt hier so eine Fassade einbauen, dann machen wir uns das
Ganze ein bisschen leichter.
Wir haben von hier oben nur noch einen Zugriffspunkt.
Wir müssen nur noch wissen, wie die Fassade aussieht.
Und die Fassade wiederum delegiert dann an die jeweilige Komponente in
der darunter liegt.
Und das hat natürlich den großen Vorteil, dass wenn ich hier irgendwas
austausche, zum Beispiel diese Komponente haben wir gesehen, dann
müssten wir jetzt hier was ändern, hier was ändern, hier was ändern,
weil die müssen ja alle direkt hier drauf zugreifen.
Wenn ich diese Fassadenlösung habe, dann ist das viel einfacher.
Dann sage ich einfach hier Fassade, ich möchte was von dieser
Komponente da unten.
Das weiß ich aber hier oben gar nicht.
Ich will nur irgendwas für mich erledigt haben.
Ich möchte irgendeinen Aufruf halt an die darunter liegende Schicht
starten.
Und die Fassade sorgt dann eben dafür, dass hier das Richtige
delegiert wird, an die richtige Komponente dieser Aufruf
weitergeleitet wird.
Und dann sehen wir natürlich schon, dass das deutlich weniger
Änderungen zur Folge hat.
Natürlich, in der Schicht haben wir immer noch das gleiche Problem,
wenn wir hier diese Komponente ändern, dann sehen wir, müssen wir die
beiden hier unten auch ändern, weil die benutzen sich gegenseitig.
Das ist hier natürlich immer noch das gleiche Problem.
Aber diese drei Änderungen, die hier oben durch diese Benutzrelationen
entstehen würden, die haben wir dann eben weg.
Deshalb benutzt man da häufig eine Fassade dazu.
Aber wie gesagt, Fassade werden wir uns nochmal genauer anschauen in
den Entwurfsmus.
Gut.
Klar soweit, warum das sinnvoll ist?
Einer hat zumindest mal genickt.
Reicht mir.
Und deshalb mache ich weiter mit klären Dienstgeber-Architekturen.
Das hat sicherlich auch schon jeder mal gesehen, diese Idee, wir haben
einen oder mehrere Dienstgeber und die bieten eben irgendwelche
Dienste an, wie auch immer die geartet sein sollen.
Und dann gibt es auf der anderen Seite Subsysteme, sogenannte
Klienten, die möchten halt irgendeinen Dienst ansprechen können,
irgendwas benutzen können.
Und Klienten rufen dann üblicherweise Funktionen des Dienstgebers auf
und der führt dann eben was aus und liefert ein Ergebnis zurück.
Dabei muss natürlich der Klient die Schnittstelle des Dienstgebers
kennen.
Ist halt einfach per Konvention offensichtlich so, weil sonst kann ich
mit dem Dienstgeber nichts anfangen.
Andersrum, und das ist natürlich dann schon ein großer Vorteil, der
Dienstgeber muss für sich die Schnittstelle des Klienten nicht kennen.
Das wäre ja auch noch schöner, es gibt vielleicht sehr, sehr, sehr
viele Klienten und dann müsste ja der Dienstgeber alles über die
wissen.
Das will man natürlich nicht haben.
Das heißt, nur der Klient kennt die Schnittstelle des Dienstgebers.
Der muss wissen, was hier Dienst 1, was hier Dienst 2 macht, aber was
auf Seiten des Klienten steht, ist dem Dienstgeber herzlich bekannt.
Wo hat man das häufig?
Naja, im Grunde überall dort, wo man irgendwas mit Frontends und
Backends baut.
Also zum Beispiel Datenbanksysteme, da hat man eine
Benutzeroberfläche, die man als Klienten umsetzt und dann eben den
Datenbankzugriff, der als Dienstgeber implementiert ist.
Und da sehen Sie schon, ich hatte ja das gleiche Beispiel gerade bei
der Schichtenarchitektur gezeigt, das geht manchmal ineinander über.
Also man kann natürlich jetzt auch Klient und Dienstgeber wieder als
Schichten sehen oder man kann den Dienstgeber wiederum als
Schichtenarchitektur implementieren.
Wir hatten da glaube ich vor zwei Jahren oder vielleicht war es auch
letztes Jahr mal eine sehr schöne Klausuraufgabe mit einer
Beschreibung einer ethnischen Situation.
Wir wollten irgendwie was haben, was Dienste zur Verfügung stellt und
dann haben wir gesagt, welches Architekturmuster passt denn hier?
Und die richtige Antwort war quasi alle, man musste es bloß halt
richtig argumentieren.
Und das war eigentlich ganz hübsch, war eigentlich eine schöne
Aufgabe, mir hat die jedenfalls gefallen, den Studenten nicht allen.
Aber ich sag's mal so, die ist auch nicht so furchtbar schlecht
ausgefallen, weil das dann doch relativ schön war zu argumentieren,
wenn man es denn konnte, was man denn jetzt hier benutzen können
wollte.
Und die meisten sind eigentlich darauf gekommen, dass man da irgendwie
eine Kombination haben will von unterschiedlichen Dingen.
Wir haben da natürlich noch so ein paar Randbedingungen aufgestellt,
sodass es ein bisschen eindeutiger wurde, was es werden sollte.
Aber nur, dass sie das eben mal sehen, dass das nicht voneinander
getrennt ist.
Also es ist nicht so, dass ich sage, hier Entwurf, jetzt brauche ich
eine Schichtenarchitektur, da darf jetzt aber kein Klient mehr
irgendwie auf einen Dienst zugreifen.
Das darf auf keinen Fall sein.
Also hier machen wir keinen klären Dienstgeber mehr.
Also häufig ist das eher so, dass sich das auch mal schneidet, dass
man das eine aus der einen Architektur nimmt und das andere aus der
anderen und das so ein bisschen kombiniert.
Das sind also alles für sie im Grunde nur Ideen, damit sie mal gesehen
haben, wie Architekturen aussehen können.
Und wenn sie dann mal selber in der Verlegenheit sind, eine
Architektur aufbauen zu müssen, dann wissen sie, was es gibt und
wissen vielleicht für ihr Problem, dass man jetzt eine
Schichtenarchitektur benutzen kann oder dass man das besser als den
Dienstgeber implementiert.
Aber dann wissen sie vielleicht auch, dass der Dienstgeber aus
mehreren Schichten bestehen kann.
Und dann wissen sie, dass man das wunderbar auch kombinieren kann,
dass das eben nicht einzeln nebeneinander steht.
So, jetzt wieder zurück zu Klient-Dienstgeber nach diesem kurzen
Exkurs.
Der Klient naja offensichtlich nimmt die Eingaben des Nutzers vor
Verarbeitung gegebenenfalls und dann darf eben der Dienstgeber die
eigentliche Datenverwaltung, Sicherheit, Datenintegrität sicherstellen
und das ist eigentlich genau der Witz von dieser Trennung.
Der Klient weiß außer der Schnittstelle nichts vom Dienstgeber.
Da geht es dann wieder um das Geheimnisprinzip.
Es geht darum, dass der Dienstgeber nicht weiß gibt, was er jetzt
eigentlich intern benutzt, sondern eben einfach nur über die
wohldefinierte Schnittstelle Dienste zur Verfügung stellt, die die
Klienten nutzen können.
So, was haben wir für Beispiele?
Naja, sowas wie FTP-Server, Klient, was wie Fallziller initiiert, eine
Dateiübertragung und Dienstgeber reagieren dann eben auf diese
Anfragen und naja, senden oder empfangen Dateien so einfach.
Also alles, was irgendwie so gebaut ist, ist eigentlich immer Klient
-Dienstgeber - Architektur.
Gut, Fragen müsst ihr her?
Da kommt eine Frage.
Ihr Kommilitone fragt, was ist da jetzt eigentlich der Unterschied zu
einer Schichtenarchitektur mit einer Fassade?
Die Antwort ist ein bisschen schwierig.
Das Wesentliche ist eigentlich so die Sichtweise oder wie man
neudeutsch sagen würde, das Mindset.
Bei der Schichtenarchitektur geht es uns eher darum zu sagen, das sind
die Schichten, wir unterteilen unsere Systeme in diese Bestandteile
und machen im Grunde Abstraktionen und Zugriffe von oben nach unten.
Und bei Klient-Dienstgeber ist der Gedanke halt eher zu sagen, wir
haben einmal einen Dienstgeber, der stellt gewisse Dinge zur Verfügung
und einmal einen, der möchte gerne etwas wissen.
Das ist einfach nur eine andere Angehensweise an das Problem.
Und der Klient ist natürlich keine Abstraktion von dem Dienstgeber.
Es ist im Grunde eigentlich, wenn Sie das als Schichtenarchitektur
interpretieren würden, sieht das von der Abstraktion erstmal
horizontal aus.
Von der Benutzt-Relation ist es natürlich eigentlich wieder vertikal,
weil der Klient benutzt ja den Dienstgeber und nicht andersrum.
Das liegt also so ein bisschen daran, was Sie bauen wollen.
Also wenn Sie irgendwie sowas bauen wollen wie eine Dateiübertragung
oder so ein Datenbanksystem und fangen an mit Ihren Kollegen darüber
zu diskutieren, wie man sowas baut, dann werden Sie sicherlich nicht
anfangen und sagen, wir bauen da jetzt eine Schichtenarchitektur und
der Klient ist die oberste Schicht.
Dann geht es erstmal darum zu sagen, wir wollen hier eine Klient
-Dienstgeber -Architektur bauen.
Dann wissen erstmal alle, worum es geht und wie man sowas strukturiert
und dass man dann eben eine Schnittstelle nach außen braucht.
Und wie man das dann weiter strukturiert, dass man dann intern
vielleicht noch eine Schichtenarchitektur bauen möchte oder dass man
diese Klient-Dienstgeber-Beziehungen auch als Schichtenarchitektur
sehen kann, das kommt dann vielleicht später.
Aber in dem Moment geht es dann erstmal darum, zu wissen, wofür das
System auch da sein soll und wie sowas grob strukturiert werden kann.
Aber ja, Sie haben recht, man kann das natürlich auch eins zu eins als
Schichtenarchitektur sehen.
Gut, noch Fragen?
Wunderbar.
Dann machen wir weiter mit einem ähnlichen Konzept, was aber dann
tatsächlich nicht mehr zur Schichtenarchitektur taugt, nämlich die
Partnernetze.
Hier haben wir die Verallgemeinerung des Klient-Dienstgeber
-Architekturstils, dieser Idee.
Und in diesem Netz sind dann einfach alle gleichberechtigt.
Das heißt, jeder ist Dienstgeber und jeder ist Klient.
Und vereinfacht sieht das so aus, dass wir eigentlich hier eine
Mehrfachvererbung hätten, wenn wir das als U- und L-Diagramm
darstellen würden von Klient und Dienstgeber zu Partner.
Geht ja in Java jetzt nicht, es sei denn, wir bauen das als
Schnittstellen.
Und wir könnten das aber so auch darstellen.
Wir haben hier einfach einen Partner und es gibt unterschiedliche
Dienste und jeder dieser Partner kann sowohl Anforderer sein und kann
auch Anbieter sein.
Und wir sehen auch, jeder kann mit beliebig vielen in Verbindung
stehen.
So sieht das dann mal optisch aus.
Das klassische Klient-Dienstgeber-Modell.
Wir haben einen Dienstgeber, der alle Anforderungen von allen Klienten
entgegen nimmt und hier die Partnernetze oder Peer-to-Peer-Variante in
dem einfach alle gleich berechtigt sind.
So, jetzt ist natürlich die Frage, wenn man irgendwie mal in der
Verlegenheit ist, so etwas zu bauen und das war übrigens auch Teil der
Klausuraufgabe, nimmt man jetzt lieber für unser System Klient
-Dienstgeber oder Partnernetze?
Auch da war die Antwort beides möglich.
Aber was sind denn so die Vor- und Nachteile, wenn man jetzt System X
umsetzen wollte?
Ich glaube, wir hatten damals ein Datenbanksystem, das wir umsetzen
wollten.
Was wäre denn da jetzt der Vorteil, wenn ich Klient-Dienstgeber hätte
und was wäre der Vorteil, wenn ich Partnernetze hätte?
Oder natürlich jeweils auch Nachteile.
Möchte keiner mitarbeiten heute?
Doch, da meldet sich jemand.
Wunderbar, vielen Dank.
Okay, also ihr Kommilitone sagt, hier ist es natürlich viel
problematischer, wenn der Dienstgeber
ausfällt.
Wir haben vielleicht hier nur einen Knoten, der alle Dienste entgegen
nimmt.
Wenn der ausfällt, ist es halt einfach vorbei.
Das Problem haben wir hier nicht.
Da haben wir das vielleicht einfach verteilt auf alle.
Und wenn dann einer ausfällt, naja, dann ist es halt weg.
Und dann können die anderen weiterarbeiten.
Da meldet sich aber schon jemand, möchte dazwischen grätschen oder was
erweitern.
Genau, das ist genau jetzt die Gegenseite des Problems, wenn wir hier
die Partnernetze haben, dann haben wir zwar vielleicht mehr
Ausfallsicherheit, wenn wir die Daten über alle verteilen, aber wir
müssen dann natürlich auch sicherstellen, dass das konsistent bleibt.
Und eine mögliche Lösung für unser Problem damals in der Klausur wäre
gewesen, Partnernetze zu bauen mit einem virtuellen Klient-Dienstgeber
-Modell darüber.
Da haben wir im Grunde drei Sachen verknüpft, wenn man das so möchte.
Wir haben also eigentlich unsere Partnernetze, bauen dann aber eine
Virtualisierung davon und zwar als Klient-Dienstgeber-Modell.
Das wäre eine mögliche Lösung gewesen, damit man im Grunde diese
konsistente Sicht auf die Daten hat.
Wie das dann funktioniert im Einzelnen, wie man diese
Konsistenzhaltung macht in der Virtualisierung, darüber muss man sich
dann wieder Gedanken machen.
Ist auch nicht so einfach, gebe ich zu, aber jetzt mal einfach auf
Architekturebene wäre sowas eine mögliche Lösung.
Also da sehen Sie auch wieder Architekturstile, die stehen irgendwie
nicht losgelöst voneinander, sondern die kann man auch schön
kombinieren, zusammenbasteln, wie auch immer.
So, jetzt schauen wir noch ein bisschen in die Partnernetze rein.
Ich wollte das bloß nicht schon zeigen, weil das so ein bisschen auch
zeigt, was die Vor- und Nachteile sind.
Wir sehen hier, wir haben eine komplette Rollensymmetrie, das heißt
jeder Partner ist sowohl Klient als auch Dienstgeber.
Das haben wir gerade eben auch schon gesehen auf dem Vorhinein davor.
Und es gibt erstmal keine zentrale Koordination und keine zentrale
Datenbasis.
Das heißt, jeder hält entweder eine komplette Kopie der Daten oder
Teile davon, je nachdem.
Und in der Regel kennt auch jeder Partner nur eine Untermenge der
übrigen Partner.
Das kann natürlich auch alle kennen.
Und das Gesamtverhalten des Systems, das organisiert sich dann halt
irgendwie selber und besteht im Grunde nur aus der Interaktion
zwischen den einzelnen Partnern.
Das wird dann halt irgendwie zusammengesetzt.
Und dadurch haben wir eine starke Autonomie.
Das heißt, jeder Partner trifft selber die Entscheidung, was er denn
jetzt eigentlich haben möchte.
Möchte er irgendwelche Dienste anfordern?
Möchte er etwas bereitstellen?
Die Wahl hat er natürlich nicht.
Er muss das natürlich jeweils tun, aber was es dann tatsächlich
ausgeführt wird, das ist dem jeweiligen Partner überlassen.
Wir haben hier aber auch den Nachteil, dass Partner eben unzuverlässig
sein können.
Ja, wird einer abgeschaltet, dann müssen irgendwie Mechanismen
gefunden werden, das zu kompensieren.
Oder man sagt im einfachsten Fall, der Partner ist jetzt halt nicht
mehr da, der kann jetzt halt den Dienst nicht mehr anbieten.
Und in Richtung Verfügbarkeit bedeutet das natürlich auch, alle
irgendwie gespeicherten Daten müssen eigentlich redundant verfügbar
sein, wenn Partner unzuverlässig sind.
Das ist aber nicht unbedingt immer umsetzbar.
Deshalb ist das natürlich auch ein Nachteil.
So, was haben wir für Beispiele?
Sowas wie BitTorrents.
Da ist jeder Partner offensichtlich Klient und auch Dienstgeber.
Jeder kann irgendwie Dateien anfordern, liefert diese aber auch.
Und wenn eben eine Anfrage von einem Partner nicht bearbeitet werden
kann, dann fragt man halt einen anderen.
Aber wer sowas schon mal benutzt hat, der weiß natürlich auch, es kann
auch sein, dass einfach kein Partner das gerade zur Verfügung stellt.
Dann müssen wir halt warten, bis wieder einer da ist.
So, was haben wir noch?
TCP-IP.
Ja, da haben wir halt auch genau diese Variante der Netzarchitektur.
Anfragen werden anwendungsunabhängig über das physikalische Netz
verteilt.
Offensichtlich.
Wer weiß, wie das funktioniert, denke ich mal.
Brauchen wir, glaube ich, auch nicht noch genauer reinschauen.
Das Beispiel mit dem BitTorrent ist offensichtlicher.
Gut.
Wunderbar.
Gibt es Fragen bisher zu den Partnernetzen?
Sonst kommen wir gleich zum nächsten.
Keiner?
Wunderbar.
Dann Datenablage.
Damit haben wir sie ja schon gezwungen, sich auseinanderzusetzen.
Datenablage, sowas wie SVN oder Git benutzen Datenablagen.
Auch Depots genannt.
Und hier ist einfach die Idee, wir haben Subsysteme und die
modifizieren irgendwie Daten und möchten diese Veränderungen in einer
zentralen Datenablage bekannt machen, dort irgendwie ablegen.
Und die Subsysteme, und das ist jetzt wieder wichtig, die sind lose
gekoppelt und interagieren nur über die Datenablage.
Also das stellt den einzigen Koppelungspunkt der einzelnen Subsysteme
dar.
Eben über die Daten, die dort abgelegt werden.
Die Subsysteme können gleichzeitig darauf zugreifen, auf die
Datenablage oder eben hintereinander.
Wenn man parallel darauf zugreift, muss man sich natürlich wieder
überlegen, gibt es da Mitte, dann haben wir ein Problem.
Wenn nicht, dann ist alles gut.
Denken Sie an unser Beispiel aus den Änderungen in einem Buch.
Da war unsere Datenablage die Bibliothek.
In der zweiten Vorlesung war das, glaube ich.
Und da haben wir gesagt, wenn wir jetzt das Buch holen und Änderungen
darin machen und machen die an unterschiedlichen Stellen zu zweit
gleichzeitig, dann ist das völlig egal.
Dann können wir die Änderungen einfach beide durchführen und dann ist
das gut.
Wenn das an gleichen Stellen ist, dann müssen wir uns halt überlegen,
wer darf das machen und wer muss seine Änderungen zurückziehen oder
können wir die Änderungen zusammensetzen.
Also das ist die Idee hinter diesem Architekturstil.
Das ist, glaube ich, auch einleuchtend.
Was haben wir hier?
Noch die genaue Erklärung dazu zu möglichen Beispielen.
Ich habe schon gesagt, GIT oder Subversion sind so übliche
Datenablagen.
Das habe ich auch schon gesagt mit den Änderungen.
Ah ja, genau.
Stichwort Transaktionen.
Wir hatten auch da in der zweiten Vorlesung schon ein schönes
Beispiel, dass Transaktionen wichtig sind.
Wer kann sich noch ändern an den Vorläufer von SVN, den wir
vorgestellt haben?
DVS, richtig.
Wir haben einfach ganz viele Änderungen gemacht und wollten die in
unserem zentralen Depot hinterlegen und dann werden die halt dahin
übertragen, eingebucht und dann stellt das System fest bei Datei 5.
Oh, die hat jemand anders schon geändert.
Das geht jetzt nicht.
Deine Einbuchung ist leider nicht möglich.
Dann wird die an der Stelle zurück gewiesen.
Das kann aber natürlich zu Inkonsistenzen führen.
Ich habe irgendwie Änderungen an vier Dateien und die fünfte darf ich
jetzt nicht machen.
Dann funktioniert das Ganze vielleicht nicht mehr.
Stellen Sie sich vor, Sie haben was programmiert und haben an vier
Klassen was geändert und die fünfte Änderung in der fünften Klasse
dürfen Sie jetzt nicht mit einbuchen, weil da eine gleichzeitig andere
Änderung passiert ist und Sie haben zum Beispiel eine Schnittstelle
umbenannt.
Dann funktioniert natürlich gar nichts mehr.
Offensichtlich.
Das heißt, das will man nicht haben, sondern man möchte ein
sogenanntes Transaktionskonzept haben, wo eben sichergestellt wird,
dass entweder das Ganze abgewickelt wird, was wir dort gemacht haben
oder nichts.
Und wer schon mal SVN benutzt hat, der wird das vielleicht gesehen
haben, wenn man da etwas einbuchen möchte und da ist nur irgendwo in
einer Datei irgendwas anders, dann wird die komplette Einbuchung erst
mal zurückgewiesen.
Da muss man sich darum kümmern, dass man das aufräumt.
Okay.
Gibt es Fragen zur Datenablage?
Keine.
Dann jetzt ein sehr häufig verwendeter Architekturstil.
Ich hatte ja schon mal angedeutet, wir machen ja immer mit unseren
Tutoren für das nächste Semester so kleine Castings.
Die kommen dann zu uns und erzählen uns so ein bisschen was über sich
und was sie an der SVT toll fanden und was nicht.
Und normalerweise, wenn sie irgendwas toll finden, so im Nachhinein,
dann ist das aus dem Kapitel.
Auch wenn ihre Gesichter, die ich jetzt gerade so sehe, anderes
vermuten lassen.
Aber häufig sind es dann doch irgendwie so die Entwurfsmuster oder die
Architekturstile, die tatsächlich irgendwie was bringen.
Ich sage mal, 50% der Fälle kommt dann genau das ModelViewController.
Das habe ich irgendwie schon mal gebraucht, das habe ich schon mal
verwendet.
Und das ist eben ein sehr häufig angewendetes Modell für die
Darstellung eines Gesamtsystems.
Strukturiert sehr gut Systeme, die irgendwie auf Daten arbeiten und
diese Daten irgendwie darstellen wollen.
Das ist möglichst flexibel und austauschbar.
Deshalb braucht man das ziemlich häufig.
Was macht das Architekturmuster jetzt?
Also angenommen, wir haben ein System und haben dort eigentlich eine
starke Kopplung und zwar zwischen Daten und einer
Benutzerschnellstelle.
Denken Sie an irgendeine grafische Benutzeroberfläche, die irgendwie
irgendwelche Wetterdaten darstellen möchte.
Dann würde man ja eigentlich von unserer grafischen Benutzeroberfläche
sagen, liebe Wetterdaten, was ist denn gerade anzuzeigen?
Und dann zeigt man das eben an.
Und das Problem, was daraus entsteht, ist, wir müssen als grafische
Benutzeroberfläche wissen, wie unsere Daten aussehen, wie unser
Datenmodell aussieht, wie wir darauf zugreifen können.
All das muss uns bekannt sein.
Und das hat dann eben zur Folge, dass so eine Benutzerschnittstelle
nicht einfach mal so eben neu implementiert werden kann.
Angenommen, wir wollen die jetzt einfach mal hübscher machen.
Wir wollen die vielleicht anders aussehen lassen.
Wir wollen vielleicht eine andere Bibliothek benutzen, in der wir
diese Benutzerschnittstelle implementieren.
Dann ist das eben nicht so einfach möglich, weil dann müssen wir alles
neu bauen, das ganze System.
Weil die Daten die Benutzerschnittstelle kennen und umgekehrt.
Und genauso gilt das dann auch für die Daten.
Die Daten können auch nicht einfach neu organisiert werden, weil wenn
ich die Daten jetzt umbaue plötzlich, die Daten-Darstellung zum
Beispiel in anderes Datenformat benutze, dann muss ich wiederum auch
meine Benutzerschnittstelle anpassen, weil die verlässt sich ja
darauf, dass das, keine Ahnung, in XML dargestellt ist oder als
Datenbank abrufbar ist.
Und das ist genau das Problem.
Wir haben dann eine ziemlich starke Kopplung zwischen Darstellung und
dem eigentlichen Datenmodell und das ist genau das, was man nicht
haben will.
Und dafür gibt es dieses Model-View-Controller-Architekturmuster.
Und da ist es so, dass wir ein Subsystem zur Datenspeicherung haben,
ein Datenmodell, das Model.
Und dann haben wir ein Subsystem zur Darstellung, das ist die View.
Und es gibt ein weiteres Subsystem, was zwischen diesen beiden
vermittelt.
Also der nimmt im Grunde dieses Subsystem, nimmt dann als Controller
Anfragen von der Darstellungs-Subsystem entgegen und sagt dann, liebes
Modell, gib mir doch bitte mal die entsprechenden Daten.
Und mit dieser Entkopplung erreicht man, dass eben diese einzelnen
drei Bestandteile austauschbar sind.
Und das sehen wir jetzt hier noch ein bisschen besser, wenn wir uns
die Übersicht jetzt mal als Paketmodell anschauen.
Wir haben ein Controller-Paket, wir haben ein Modell-Paket, wir haben
ein View-Paket, die alle irgendwie in Verbindung stehen.
Und das Modell, das hatte ich schon gesagt, ist dafür zuständig, die
Daten darzustellen.
Die Präsentation ist zur Darstellung der Objekte da und der Controller
steuert jetzt eben genau diese Interaktion zwischen Darstellung und
den Daten.
So, ist das jetzt hier ein Schichten-Modell?
Mal so nebenher, weil wir die Frage ja gerade eben schon hatten, kann
man das nicht auch als Schicht bauen?
Ja, nein, vielleicht meldet sich jemand.
Ah, sie waren ja schon so häufig da hinten.
Besonders weit hinten ist es nicht, aber sie dürfen trotzdem.
Ja, genau sie.
Ist es ein Schichten-Modell?
Okay, warum?
So wie es jetzt hier gezeichnet ist?
Also austauschen, ja kann man es, aber...
Ja, man muss aufpassen, dass die Schnittstellen passen.
Und was haben wir noch für ein Problem?
Vielleicht möchten Sie ergänzen.
In der Darstellung, also in der Darstellung kann es sein, wir schauen
uns gerade nur die Pakete an.
Darstellung und Modell aufeinander basieren oder Controller oder was
meinen Sie jetzt?
Genau.
Also, vollkommen richtig.
Hier können wir tatsächlich theoretisch Zyklen drin haben.
Das wäre schlecht.
Es ist aber tatsächlich nicht zwingend hierarchisch.
Also der Controller kann etwas in der View verändern.
Der Controller greift auf das Modell zu, Modell vielleicht auf
Controller.
D.h.
diese drängen Hierarchien haben wir hier nicht.
Die Frage ist aber viel eher, könnte man es als Schichten-Modell
bauen?
Wer ist denn der Meinung, dass sich sowas umsetzen lässt als Schichten
-Modell?
Wer ist der Meinung, diese Zugriffe zwischen den Schichten hin und
zurück sind erforderlich, damit man das umsetzen kann?
Keiner meldet sich zaghaft.
Also die, die sich zuerst gemeldet haben, die haben recht.
Man kann sowas streng hierarchisch an den Schichten aufbauen, aber und
deshalb hat auch der eine, der sich so zaghaft gemeldet hat, nicht
ganz unrecht.
Das ist so einfach.
Häufig braucht man eben diese Rückkopplung, weil man ja auch Dinge
wieder darstellen können möchte in der Darstellung.
D.h.
man hat eine gewisse Rückkopplung zwischen Modell und Darstellung.
Und das ist häufig das Problem.
Man kann das natürlich über den Controller schicken, aber auch dann
kann es sein, dass man eben nach oben wieder greifen muss und eben
nicht strecken und nach unten.
Um das ein bisschen schöner zu machen, kann man häufig, und jetzt geht
es wieder in Richtung Entwurfsmuster, das Entwurfsmuster Beobachter
benutzen.
Und da ist es so, dass das Modell
die Präsentation darüber informiert, dass etwas geändert wurde.
D.h.
der Beobachter sagt dann die Präsentation ist der Beobachter, der die
Daten beobachtet in einer gewissen Art und Weise und immer wenn sich
etwas ändert im Modell, dann wird eben die Präsentation, also der
Beobachtende, informiert darüber, dass sich etwas geändert hat.
Und der Aktualisierungsvorgang wird üblicherweise dann über die
Steuerung, also über den Controller veranlasst.
Das kann man jetzt in unterschiedlichen Arten und Weisen bauen.
Stärker hierarchisch, weniger stärker hierarchisch und man kann sich
überlegen, ob jetzt der mittlere Teil auf die Steuerung dann für
diesen Rückruf transparent ist oder intransparent.
Aber sowas könnte man bauen.
Das sehen wir dann, wenn wir das Entwurfsmuster Beobachter uns genauer
anschauen, um eben genau diese Zugriffsprobleme bei dem
Architekturmuster hier ein bisschen zu verschönern.
Dieses Beobachter-Entwurfsmuster wird tatsächlich ziemlich häufig
eingesetzt, wenn es um grafische Benutzeroberflächen geht.
Und wenn ich Ihnen jetzt sage, dass das Observer heißt, dann wissen
Sie vielleicht auch eher, worum es geht im Englischen und dann haben
Sie das auch sicherlich schon mal gehört.
Ja bitte, da kommt noch eine Frage.
Ja genau, also
Ihr Kommilitone beschwert sich so wie wir das jetzt hier
aufgeschrieben haben, ist das ja irgendwie auch nicht so ganz klar und
er beschwert sich insbesondere, dass es ungefähr 5000 Definitionen von
Model -View-Controller gibt.
Ja, das ist richtig.
Je nachdem, wie strikt man das eben auslegt, das habe ich ja schon so
ein bisschen angedeutet und wie man das tatsächlich genau aufbaut.
Und dann ist die Frage, gibt es da einen richtig?
Und die Antwort ist einfach nein.
Also das gibt es nicht.
Es kommt ja sehr auch natürlich darauf an, wofür Sie das einsetzen
wollen.
Und gerade wenn wir uns auf der Architekturebene bewegen, dann gibt es
einfach nicht die richtige Antwort für, so müssen Sie das jetzt genau
bauen.
Fängt ja häufig sehr davon ab, wie Ihre Anwendungsdaten aussehen, wie
Ihre Anwendung aussieht, wie die auch mit anderen Subsystemen
gekoppelt ist, wie Sie das dann genau bauen.
Deshalb gibt es da einfach nicht diese eine richtige Lösung.
Und das ist auch eigentlich nicht das Wesentliche bei diesem
Architekturmuster, wie denn da die Kopplung zwischen den einzelnen
Paketen ist, sondern alleine diese Aufteilung in die einzelnen Pakete
ist im Grunde das Wesentliche, dass man das zumindest austauschbar
baut.
Und je nachdem, wie stark dann natürlich die Kopplung zwischen den
Paketen ist, ist es natürlich mal besser und mal weniger gut.
Allein um die Aufteilung geht es erstmal voran.
Ihr Kommilitone fragt, ist das jetzt so ein Meta-Pattern mit möglichen
Ausprägungen?
Die Antwort ist im Grunde genau das, was ich gerade eben schon gesagt
habe.
Ja, das gilt aber für Muster im Allgemeinen.
All das, was wir Ihnen jetzt hier in Architekturstilen zeigen, das
können Sie nie irgendwie eins zu eins anwenden.
Das ist immer irgendwie ein bisschen Meta.
Sie müssen das immer konkretisieren für Ihren Anwendungsfall, für
Ihren konkreten Entwurf.
Bei Architekturstilen ist es noch viel schlimmer als bei
Entwurfsmustern, aber auch da ist das so.
Wenn wir uns dann Entwurfsmuster anschauen wollen, die sind häufig
ziemlich konkret und da steht ziemlich genau da, was da in den
Schnittstellen stehen soll und wenn Sie es dann das erste Mal
implementieren, werden Sie feststellen, vielleicht für Ihre Anwendung
funktioniert das so gar nicht.
Sie müssen das leicht abwandeln.
Und insofern bin ich ganz froh über diese Frage, weil das ist ganz,
ganz wichtig, dass man das versteht, dass das alles, was wir Ihnen
hier zeigen, so Hinweise sind.
So kann man es machen und das ist eine mögliche Lösung, ein System zu
strukturieren, eine Architektur zu bauen und das ist eine mögliche
Lösung für einzelne Entwurfsentscheidungen, aber das ist nie so, dass
es genau das ist, was Sie später mal anwenden können, sondern Sie
müssen das immer auf Ihr konkretes Problem anpassen.
Gut.
Noch weitere Fragen bis hierher?
Ansonsten würde ich ganz gerne noch die anderen Architekturstile kurz
durchbringen.
Dann machen wir weiter mit einem, was Sie vielleicht schon in der
Übung gesehen haben, wenn Sie denn die letzte Aufgabe auf dem
Bildungsblatt gemacht haben, nämlich ein Fließband zu bauen.
Da sollte man mal eine Schnittstelle für so ein Fließband bauen.
Und das ist tatsächlich auch ein Architekturstil und dort sieht es so
aus, dass wir mehrere Stufen oder Filter haben, je nachdem, wie wir
das benennen wollen und jeder davon ist irgendwie eigenständig.
Jeder Filter macht sein eigenes Ding, bearbeitet irgendwas und die
Idee ist dann, Daten durch dieses Fließband durchlaufen zu lassen und
jede Stufe empfängt die Daten von dem davor, verarbeitet sie und
reicht sie weiter an den nächsten.
Und zwischen den Stufen gibt es üblicherweise Puffer, weil die eine
Stufe kann mal schneller sein als die andere, das heißt Stufe 1
arbeitet super schnell und Stufe 2 ist ziemlich langsam.
Dann müssen wir natürlich dafür sorgen, dass Stufe 1 weiterarbeiten
kann und Stufe 2 auch in seiner langsamen Ausführung weitermachen kann
und trotzdem Stufe 1 nicht angehalten werden muss.
Und dafür gibt es normalerweise Puffer und dann merken Sie schon, in
welche Richtung das geht.
Das geht nämlich tatsächlich in Richtung Parallelverarbeitung, denn
normalerweise macht man das so, dass man diese einzelnen Stufen in
einzelnen Fäden implementiert und dadurch sind die einfach unabhängig
voneinander und können einfach parallel laufen und dann braucht man
eben Puffer, können Sie sich vorstellen, Faden 1 sehr schnell legt
hier Elemente ab, Stufe 2 sehr langsam im zweiten Faden kann die
Elemente gar nicht so schnell abarbeiten, wie sie dann da wieder
reinkommen.
Man kann das natürlich auch sequenziell umsetzen.
Ja, dann nutzt man dieses Architekturmuster dafür, um
Datenverarbeitung sinnvoll zu strukturieren.
Wir haben das ja jetzt auch schon gemacht in unserem iMatch Projekt
mit diesen Bildern.
Wenn ich auf Bildern unterschiedliche Operationen durchführen möchte,
dann lasse ich das normalerweise auch durch so eine Pipeline
durchlaufen.
Weniger, weil ich es müsste.
Ich könnte auch diese ganzen Operationen einfach hintereinander
hängen, aber wenn ich das später mal noch besser modellisieren möchte,
weitere Operationen zufügen möchte, dann ist es einfach deutlich
änderungsinvarianter, wenn ich sowas definiert habe wie Filter und
eine Pipeline-Struktur.
So, wir bleiben aber mal bei der Parallelverarbeitung und da sehen wir
dann auch den Vorteil, wenn wir das, ich blende das mal gleich ganz
auf, wenn wir das über ein Fließband definiert haben und diese
einzelnen Verarbeitungsschritte dann haben, dann sehen wir, wenn wir
jetzt jede Stufe als einzelnen Faden implementiert haben, dann gehen
wir einfach mal hier an den Zeitschritt 4, dann sehen wir, dass dort
in der Stufe 1 vom Faden 1 eben das vierte Bild zum Beispiel
verarbeitet wird.
Der Faden 2 ist noch beim dritten Bild.
Der Faden 3, genau, beim dritten Bild.
Nein, ich war hier.
War schon richtig.
Also Stufe 2 ist beim dritten Bild, Stufe 3 ist beim zweiten und Stufe
4 ist erst beim ersten, weil man muss ja erst mal warten, bis das
erste Bild durch die einzelnen Vorgängerstufen durchgelaufen sind.
Das heißt, am ersten Zeitschritt hat die Stufe 4 auch einfach mal nix
zu tun.
Genauso beim zweiten und beim dritten.
Erst beim Zeitschritt 4 kommt überhaupt die Stufe 4 mal dazu,
irgendwas zu arbeiten, arbeitet dann natürlich auch wieder am
längsten.
Das heißt, es lohnt sich eigentlich erst so richtig, wenn wir viele
Daten haben, die wir hintereinander verarbeiten wollen und deshalb ist
so ein Standardanwendungsfall auch Bildverarbeitung,
Bildstapelverarbeitung oder vor allem auch Signalverarbeitung
natürlich, auch Videosignale werden häufig mit so einer Pipeline
verarbeitet, eben weil man da einfach sehr viele Daten hat, die auf
eine gleiche Art und Weise gleich verändert werden.
Gut, was ist noch so ein typisches Beispiel?
Das hat vielleicht jeder schon mal gesehen.
Die Pipes in Unix-Systemen hier haben wir mal so ein Beispiel
dieses PSAUW das gibt Ihnen einfach alle laufenden Prozesse dann
schieben wir das in die nächste Stufe rein, die sagt grepp Hans,
filter mir alle Teile mit Hans heraus, dann sortieren wir das noch
vielleicht auf irgendeine Art und Weise und letztlich in der letzten
Stufe lassen wir es uns noch so anzeigen, dass wir da auch
durchblättern können Ja, also das ist auch eine typische Pipeline und
ich hatte es gerade eben schon gesagt, Signalverarbeitung,
Foyertransformation Signalmanipulation und dann wieder inverse Foyer
sowas könnte man auch als Pipeline sehen, natürlich auch fast immer
als Pipeline implementiert.
Gut, hier habe ich auch schon gesagt, gut geeignet für Datenströme,
Stabelverarbeitung für Übersetzer natürlich auch, Videokodierung und
wichtig ist für die Leistung auf parallel rechnen, dass die Stufen
möglichst gleich schnell laufen wenn die Stufen sequenziell laufen auf
Einzelprozessionen, ist das quasi egal aber wenn wir Parallelrechner
haben, dann wollen wir natürlich, dass die Stufen ungefähr gleich
schnell laufen, damit eben die Puffer, die wir da vorhin definiert
haben, nicht volllaufen.
Hat jemand eine Idee, was man machen könnte, wenn das nicht der Fall
ist?
Schon sehr weit vorgegriffen, die Parallelverarbeitung, aber
vielleicht hat jemand eine Idee, was man tun könnte, wenn man sich
einfach das Muster anschaut was passiert, wenn Stufe 2 furchtbar
langsam ist und Stufe 1 furchtbar schnell, was könnte man da tun um
dem Abhilfe zu schaffen?
Ja, dass Sie das wissen, vermute ich schon fast, aber vielleicht weiß
es auch derjenige dahinter Ihnen
Okay, also eine Möglichkeit wäre, in kleinere Stufen aufteilen, hilft
uns das?
Was meinen Sie?
Oder, ja doch Bitte, zweite Okay Okay, jetzt haben wir zwei Varianten
Nehmen wir die beiden nochmal zur Abstimmung, wer jetzt entscheidet
Ein Kommilitone sagt, wir halbieren Stufe 2 machen zwei A und zwei B
draus Kommilitone B sagt, wir duplizieren Stufe 2 Wer ist denn für die
erste Variante?
Wer findet die erste Variante besser mit der Aufteilung?
Nicht mal er selber, das ist schade Aber fünf haben sich gemeldet, wer
findet Variante B besser?
Okay, es sind auch nicht viel mehr aber ein paar.
Gut, ich gebe zu ist jetzt auch wirklich zu dem Zeitpunkt eine
schwierige Frage Tatsächlich würde man Variante B nehmen Also wenn man
einfach eine langsame Stufe 2 hat, dann sagt man einfach wir bauen
eben zwei Instanzen von der Stufe 2 und lagern die auf zwei Fäden.
Dann müssen wir uns dann nur sehen, wie wir die Puffer sinnvoll
synchronisieren können, aber an sich ist das eine gute Lösung
Aufteilung hilft auch natürlich, weil wenn wir es kleiner schneiden
das Problem, kann es sein, dass wir eben genau diese Duplizierung
vielleicht einfach zielgenauer machen könnten.
Also wenn wir 2,1 und 2,2 bauen, dann sehen wir vielleicht, dass nur
der zweite Teil langsam ist und müssen nur den duplizieren.
Das kann durchaus auch helfen, um das Problem in den Griff zu kriegen.
Gut, klar soweit, dann machen wir mal weiter mit der Rahmenarchitektur
Das ist ja tatsächlich auf dem aktuellen Übungsblatt von Ihnen
gefordert in sehr vereinfachter Art und Weise eine Rahmenarchitektur
zu benutzen und die Idee der Rahmenarchitektur oder English Framework
ist es, ein nahezu vollständiges Programm zu bauen was aber Lücken für
Erweiterungspunkte bietet.
Und das kann soweit gehen, dass wir unter vollständigem Programm
lediglich verstehen eben die Aneinanderreihung der jeweiligen
Einschübe Das heißt, es ist dann nur noch Ablaufsteuerung.
Wann möchte ich welchen Einschub benutzen?
Klassisches Beispiel dafür ist Eclipse.
Eclipse besteht quasi nur aus Einschüben Ein System, was nur aus
Plugins besteht im Wesentlichen und dieses vollständige System, was
wir da oben oder vollständiges Programm, das wir da oben stehen haben
enthält die Anwendungs Logik und meistens auch ein mögliches
komplettes Hauptprogramm das muss aber gar nicht der Fall sein, wenn
wir da einfach genug Einschübe haben für jede Stelle, dann brauchen
wir das nicht mal und von einigen Klassen im Programm können jetzt
Nutzer, Unterklassen bilden und Methoden überschreiben und damit im
Grunde Einschübe zur Verfügung stellen und das sieht dann wie folgt
aus.
Erstmal, wie ist es ursprünglich oder wie ist es normalerweise
Normalerweise entwickeln wir Software gegen Bibliotheken, das heißt
wir bauen irgendwie was, bauen unser System von vorne bis hinten
wissen aber, es gibt irgendwo gewisse Bibliotheken die uns Logik zur
Verfügung stellen, dann benutzen wir die Wir haben ja ein
Hauptprogramm mit Anwendungslogik und sagen hier vollständige
Bibliothek, gib mir bitte keine Ahnung, was ist die Quadratwurzel von
zwei Zahlen Ja, sowas benutzen wir halt und bauen uns dadurch unser
System zusammen Anders sieht es aus, wenn wir so eine
Rahmenarchitektur bauen da stellen wir unsere Anwendungslogik dieser
Rahmenarchitektur zur Verfügung und das ist natürlich einfach so ein
einmal umdrehen in der Gedankenwelt also wir sind nicht mehr Herr
unseres Programms, wir können nicht mehr sagen erst eins, dann zwei,
dann drei, dann vier, sondern wir sind so eine Art Dienstgeber wir
sagen, wir bieten folgendes an oder für das was du dort anbietest an
deiner Schnittstelle, geben wir jetzt folgende Implementierung, aber
wann wir da aufgerufen werden, ob wir da überhaupt jemals aufgerufen
werden, das wissen wir ja nicht Wir sagen nur, wenn das passiert dann
mache ich folgendes.
Das ist die Idee Kennt man auch als Hollywood Prinzip Don't call us,
we call you.
So kann man das auch mit den Einschüben sehen Wir sind ein
aufstrebendes Sternchen in Hollywood und sagen hier, ich möchte gerne
in dem Film mitspielen, ich bin besser als George Clooney bitte
besetzt mich doch.
Und dann sagen die normalerweise Ja, war ein schönes Vorsprechen,
bitte rufen sie uns nicht an Wir melden uns dann bei ihnen, falls wir
eine Rolle für sie haben Und nach dem Prinzip funktioniert das mit den
Einschüben auch Ich kann mich da bereitstellen als Einschub Ich kann
sagen, hier ich bin ein Plugin, ich implementiere folgendes Ob das
jemals überhaupt benutzt wird vom Hauptprogramm das weiß ich nicht.
Man geht natürlich eher davon aus das ist vielleicht der Unterschied
zu Hollywood, dass man da jemals aufgerufen wird Das ist natürlich bei
so einem Plugin deutlich wahrscheinlicher aber prinzipiell gilt, ob
das jetzt aufgerufen wird oder nicht, das ist auch völlig unerheblich
Also irgendwas stellt man halt bereit Gut, nehmen wir mal ein Beispiel
mit einem Zeichenprogramm und in diesem Zeichenprogramm gibt es
Linien, Rechtecken, Ellipsen und so weiter und der Benutzer darf dann
neue Unterklassen definieren und die machen dann sowas wie Quadrat
oder eine Ikone und das Rahmenprogramm, Rahmenarchitektur, sorgt dann
dafür, dass diese Objekte richtig erzeugt werden und vielleicht noch
irgendwie dargestellt werden, verschoben werden können und so weiter
und so fort.
Und das sieht dann ungefähr so aus.
Also wir haben hier unsere Rahmenarchitektur, da ist irgendwie eine
Anwendung und Anwendung hat eine Schnittstelle wo der abstrakte
Klasse, wir wissen es hier leider nicht in der UML-Darstellung,
Zeichen und Lösche gerade egal, ob wir das als abstrakte Klasse oder
Interface haben und es gibt dann einige Formen, die schon
implementiert sind in der Rahmenarchitektur und jetzt hier nochmal der
Hinweis, das kann so sein.
Oh, jetzt los.
Nochmal der Hinweis darauf, das kann sein, muss aber nicht Ah ja, da
Never use a Windows System Automatisches Update Das macht mir aber gar
nichts, ich mache einfach ohne Bild weiter Also wir haben es ja uns
alle eingeprägt, wie das aussieht.
Wir haben eine Schnittstelle, die zur Verfügung gestellt wird und wir
haben da gewisse Implementierung darunter.
Wir haben unsere Grafik, darunter wird implementiert die
das Rechteck oder aber auch die Ellipse haben wir dort gesehen und
jetzt wieder der Hinweis, das muss nicht der Fall sein, wir können
dort auch einfach nur die Schnittstelle zur Verfügung stellen, wir
können aber auch schon konkrete Implementierung vorgeben und dann
einfach nur sagen, ok Nutzer, du kannst das erweitern und wir haben
jetzt gerade eben auch schon gesehen es gibt dann ein zweites Paket,
die Erweiterung und in dieser Erweiterung wurde eben ein Quadrat oder
eine Ikone implementiert und wir haben das ja auch schon gesehen bei
unserer Implementierung von JMJST, da haben wir jetzt auch gesagt, wir
geben Ihnen eine Rahmenarchitektur vor oder Sie dürfen auch noch Teile
davon bauen, in diesem Plug-In Management haben wir das genannt und in
diesem Plug-In Management müssen Sie im Grunde dafür sorgen, dass die
einzelnen Einschübe dann auch richtig aufgerufen werden und das ist
immer dann der zweite Teil der Wahrheit, die Rahmenarchitektur, die
muss dafür sorgen, dass diese Plug-Ins dann auch verwendet werden
können und dann auf der anderen Seite sollten Sie ja auch noch ein
Plug -In, ein konkretes implementieren, das war dieses Jahr das
Instagram Plug-In und das macht eben genau das, das implementiert eine
Schnittstelle, die man kennt, die dann verwendet werden können soll,
wenn denn das Plug-In aufgerufen wird und das zeigt Ihnen im Grunde
ganz genau, wie dieses Zusammenspiel zwischen Rahmenarchitektur und
Plug -In funktioniert und ich hatte schon gesagt, ein mögliches
Beispiel ist auch Eclipse, das haben wir auch noch auf den Folien
vorbereitet, das überspringe ich jetzt einfach gleich, wenn die Folien
wieder da sind und Sie dürfen sich das selber anschauen.
Ich erzähle Ihnen nur so viel, dass man natürlich noch ein bisschen
was tun muss, damit das auch funktioniert.
Also die Schnittstelle, nein anders, die Implementierung in der
Rahmenarchitektur, die muss natürlich wissen, wie denn die Plug-Ins
aussehen.
Er muss ja, damit man die auch aufrufen kann, wenn wir uns das mal
später in der Implementierung anschauen, muss natürlich wissen, wie
man denn jetzt die einzelnen Sassen instanziiert.
Das heißt, wir müssen da irgendwie Informationen dann doch darüber
haben, also ganz so einfach geschenkt ist es eben nicht.
Und ja, ich bin tatsächlich wieder da, gleich mit den Folien.
Dann kann ich die doch noch einblenden.
Auf geht's.
Genau, das ist jetzt hier die Eclipse-Folie.
Und da sehen wir schon, wir müssen, wenn wir jetzt, ja Eclipse ist
jetzt unsere Rahmenarchitektur, müssen wir natürlich jetzt auch den
Namen irgendwie haben für unsere Erweiterung, was an der Stelle für
sie wichtig ist, das steht halt in dieser Manifestdatei.
Das wird hier in diese Manifestdatei reingeschrieben, wie denn diese
Erweiterung heißt und in der Rahmenarchitektur schaut man dann eben da
rein, holt sich daraus den Namen und kann dann wunderbar eine neue
Instanz davon anlegen.
Und so funktioniert das im Wesentlichen.
Wir haben uns das ein bisschen vereinfacht in der Vorlesung, indem wir
diese Hilfsbibliothek verwenden, die eben die Manifestdatei so baut,
wie sie aussehen soll.
Und ich würde Ihnen strengestens empfehlen, wenn Sie mal Plugins
bauen, immer so eine Bibliothek zu verwenden, weil ansonsten muss man
sich halt auch um einiges kümmern, dass die Manifestdatei so aussieht,
wie sie aussieht.
Auch wenn im Allgemeinen natürlich reicht zu sagen, wie der Name der
auszuführenden Klasse ist.
Ne, zur instanzierenden Klasse und so rum.
Gut.
Wunderbar.
Da steht das nochmal zusammengefasst, was ich gesagt habe.
Anwendbarkeit von Rahmenarchitekturen.
Immer dann, wenn wir irgendwie eine Grundversion zur Verfügung stellen
wollen, die schon für sich funktionsfähig sein soll und dann nur noch
erweitert werden soll, wenn man das denn irgendwie möchte.
Bei JMJST machen wir das ja gerade wunderschön, indem wir sagen, das
JMJST, so wie das da ist, ist ja schon wunderbar.
Schöner Code.
Aber wir wollen da noch irgendwie Zusatzfunktionalität haben, deshalb
bauen wir da so eine Plugin-Funktionalität noch rein.
Wenn man diese Folie sieht, kann man natürlich auch fragen, ist das
so, wie das Eclipse macht, sinnvoll?
Also eine Grundversion bauen, die nur mit Plugins funktioniert und
dann einfach schon in der Standardinstallation 5000 Plugins mit
ausliefern.
Kann man natürlich so machen.
Inwieweit das sinnvoll ist, ist natürlich die Frage.
Man kann damit aber natürlich sehr schön auch Software strukturieren.
Richtig ist der Punkt hier.
Wir wollen komplexe Anbindungslogik nicht neu implementieren.
Das ist immer dann sehr schön, wenn man einfach sagen kann, die
Grundfunktionalität, die soll einfach so ablaufen.
Also diese ganze Darstellung zum Beispiel bei Eclipse.
Wo sind die Knöpfe und was kann man auswählen?
Das ist alles schon da, auch ohne Plugins.
Und egal welche Plugins ich da reinlade, das ändert sich einfach
nicht.
Und wir können sehr, sehr viel neue Funktionalitäten hinzufügen, ohne
was an der Grundstruktur ändern zu müssen.
Um das richtig anzuwenden, und jetzt kommt wieder der Verweis auf das
nächste Kapitel, benutzt man häufig Entwurfsmuster, nämlich Strategie,
Fabrikmethode, abstrakte Fabrik- und Stablonmethode.
Und das werden wir dann sehen, wie wir das verwenden können, um das
umzusetzen.
Zuletzt aber noch eine Architektur, die ich Ihnen noch kurz zeigen
möchte, und zwar SOA.
Wer hat schon mal was von SOA gehört?
Von Service Oriented Architecture?
Ja, werden auch immer weniger.
SOA wird irgendwie immer weniger hip.
Gut.
Kurz zu SOA.
SOA ist ein Architekturstil, bei dem Anwendungen aus unabhängigen
Diensten zusammengestellt werden sollen.
Und damit ist SOA ein abstraktes Konzept einer Softwarearchitektur.
Das heißt, wir sind jetzt wieder eher bei den ganz abstrakten Dingen
angekommen.
Und die Idee war im Grunde Dienste als das zentrale Element eines
Unternehmens zu betrachten, und zwar auf beiden Seiten eines Dienstes.
Sowohl wenn ich Dienstanbieter bin, wenn ich etwas bereitstelle, dann
ist das meine zentrale Aufgabe, als auch jemand, der diesen Dienst
benutzt.
Die Idee war, dass man irgendwann einfach nur sämtliche
Anwendungslogik durch das richtige Zusammenschieben von Diensten von
anderen Dienstanbietern erzeugen kann.
Und dafür braucht man eben eine Dienstbeschreibung.
Und wenn wir jetzt bei vielen Dienstanbietern sind, dann wird klar,
dass das eben mit Schnittstellen nicht mehr getan ist.
Wir haben ja vorhin schon gesagt, Klient, Dienstgeber.
Der Klient muss die Schnittstelle des Dienstgebers wissen.
Und darüber müssen wir uns jetzt irgendwie hinwegsetzen, weil wir
wollen vielleicht das unabhängig von der Schnittstelle machen oder
noch besser unabhängig von einer konkreten Programmiersprache.
Das heißt, wir müssen diese Dienstbeschreibung irgendwie noch anders
standardisieren, um das Ganze völlig sprach- und technologieunabhängig
zu machen.
Die benötigte Funktionalität eines Dienstes kann dann eben genau zur
Laufzeit eingebunden werden und das Ganze sollte dann im Grunde
dynamisch ganze Systeme zusammenbauen.
Wie sieht das Ganze dann aus?
Wir sind ein Dienstanbieter und wir schreiben unser angebotener Dienst
in ein sogenanntes Dienstverzeichnis.
Das ist das, was wir anbieten wollen als Dienst.
Und dann gibt es auf der anderen Seite Dienstnutzer, die normalerweise
viele Dienste zusammenstecken, um einen anderen großen Dienst wiederum
zu leisten, was auch immer das sein möchte.
Und die schauen dann einfach im Dienstverzeichnis nach.
Was gibt es denn da für Dienste, die uns das liefern?
Holen sich den Verweis auf den Dienst.
Abfrage dann direkt beim Dienstanbieter durch die Information, die man
aus dem Dienstverzeichnis geholt hat.
Und dann kann man eben den nutzen.
Und wenn man das jetzt richtig macht, dann sagt man nicht einfach nur
veröffentlichen, suchen und Nutzung, sondern man sagt halt, oh, das
Ganze hat auch noch irgendwie coole Namen.
Ganz wichtig immer in der Informatik.
Nicht nur was bauen, sondern coole Namen dazu überlegen.
Akronyme, die keiner versteht.
Und da haben wir zum Beispiel hier die WSDL, die Web Service
Description Language, in der das vereinheitlicht wurde, wie denn so
ein Dienstverzeichnis Eintrag aussehen muss.
Dann haben wir das UDDI, das Universal Description Discovery and
Integration Verzeichnis und dann eben SOAP.
Das ist ja schon mal besonders schön.
Das heißt ja sogar noch was, deshalb hat man das auch nicht geändert.
Das hieß ursprünglich mal Simple Object Access Protocol, bis man
festgestellt hat, dass es weder simple noch hat das besonders viel mit
Objekten zu tun.
Und dann wurde der Name 5-6 mal geändert und inzwischen weiß keiner
mehr, was das eigentlich bedeuten soll, außer SOAP.
Wichtig ist aber für Sie eigentlich nur, man muss diesen Dienst
irgendwie veröffentlichen, man muss ihn irgendwie zugreifbar machen in
seinem Dienstverzeichnis dann irgendwie standardisiert darauf
zugreifen können eben über so ein Protokoll wie SOAP und schon hat man
im Grunde dieses Schnittstellen Problem mehr oder minder gelöst.
Man hat es ja eigentlich nur woanders hin verschoben, nämlich in
dieses Dienstverzeichnis.
Das ist die andere Hälfte der Wahrheit, aber man kann es eben
versuchen dadurch noch mehr zu standardisieren über Sprachen und über
Technologien hinweg.
Letzte Folie.
Wofür ist das gut?
Die Kopplung wird immer loser, das war ja die Idee, die man damit
hatte.
Man kann Dienste hoffentlich sogar zur Laufzeit ersetzen.
Das war die Hoffnung oder ist immer noch die Hoffnung, dass sowas
geht.
Vor allem möchte man, wenn man sowas baut, Geschäftsprozesse noch
besser darstellen können.
Die einzelnen Dienste kapseln im Grunde geschäftsrelevante
Funktionalität und dann kann man das einfach zusammenbauen.
Das hatte ich schon gesagt.
Und zuletzt Verwendung von in Klammern offenen Standards.
Die Bereitstellung von Diensten nicht mehr über Schnittstellen,
sondern eben über diese plattformunabhängige Beschreibung in dem UDDI.
Gut.
Gibt es noch Fragen dazu?
Sehr schön.
Dann sind wir für heute durch und nächste Woche geht es dann weiter
mit den Entwurfsmustern.
Ein schönes Wochenende.