Sie waren mit meinen Mitarbeitern stehen geblieben im Entwurf.
Wir schauen uns dazu an.
Ich mache jetzt noch eine ganz kurze Wiederholung.
Ich erinnere Sie, dass der Entwurf zur Aufgabe hatte, eine sogenannte
Software -Architektur zu erzeugen.
Die Software-Architektur besteht aus mehreren Teilen, nämlich einer
Spezifikation der Erstzergliederung in die einzelnen Komponenten.
Dann, wie Sie zusammen Ihre Spezifikationen, nämlich welche Methoden,
welche Klassen, welche Konstanten, welche Variablen, was immer Sie zur
Verfügung stellen.
Die API, die Application Programming Interface aller Komponenten, die
wir haben.
Das wäre der Punkt 2, der hier.
Dann die sogenannte Benutztradition.
Dazu werden wir heute noch etwas sagen.
Das ist keine große Geschichte.
Die sagt lediglich, welche Komponenten andere Komponenten mitbenutzen
dürfen und in anderen Worten deren Klassen, Instanzien, Methoden
aufrufen können und so fort.
Da werden wir darauf achten müssen, dass diese Benutztradition keine
Zyklen hat.
Denn sonst haben wir den Fall, dass wir nicht einzeln testen können,
nicht schrittweise testen können.
Das wäre die Benutztradition.
Und dann kommen noch zwei optionale Punkte, nämlich der sogenannte
Feinentwurf.
Der Feinentwurf ist besonders dann wichtig, wenn man in einer sehr
niedrigen Programmiersprache programmiert, zum Beispiel Assembler,
dann würde man im Feinentwurf die Datenstrukturen vorgeben und im
sogenannten Pseudocode hinschreiben.
Pseudocode ist nichts weiter als eine Programmiersprache, in der Sie
mit Deutsch oder Englisch oder einer anderen Sprache gewisse
Anweisungen angeben.
Aber die Kontrollflussanweisungen, wie zum Beispiel if, while und so
weiter, kommen alle aus einer höheren Programmiersprache.
Dann muss man eben hergehen und den Pseudocode umwandeln per Hand in
eine niedrigere, meistens dann eine sogenannte Assembler-Sprache, die
ja nur noch auf einzelnen Maschinenbefehlen beruht.
Und ein zweiter optionaler Punkt ist die Verteilung der
Softwarekomponenten auf Subsysteme.
Meistens werden Sie denken, wozu denn das?
Aber wenn Sie heute eine Web-Anwendung bauen, dann wissen Sie sofort,
dass der Klient auf einem Rechner oder auf vielen Rechnern läuft und
der Dienstgeber auf einem Zentralrechner ist.
Da haben Sie tatsächlich schon eine Verteilung.
Das war also der Punkt.
Und dann war der...
Mal schauen, meine Notizen hier mir noch besorgen.
Dann springe ich zur Folie 12.
Dann ging es um den sogenannten modularen Entwurf.
Wir machen da eine Unterscheidung.
Der modulare Entwurf, der sieht also vor, der ist also gewissermaßen
vor OO, prehistorisch, vor Objektorientierung.
Und da heißen diese vier Komponenten, nämlich die Aufgliederung in
Einzelkomponenten, der sogenannte Modulführer oder auch der
Grobentwurf oder auch der Module Guide oder die Softwarearchitektur.
Das ist genau das Gleiche wie vorhin, aber man spricht hier nicht von
Klassen und Objekten, sondern nur von Modulen.
Das Modulbegriff haben wir schon gesehen.
Die Komponenten haben natürlich auch Schnittstellen.
Die API der einzelnen Module, Application Program Interface oder
einfach Module Interfaces, enthält also die genaue Beschreibung
dessen, was das Modul zur Verfügung stellt.
Das ist ja vor OO, also keine Klassen, sondern nur noch einzelne
Funktionen oder globale Variablen.
Insbesondere ist das bei C und C++ der Fall.
Und falls es sich um Ein-Ausgabe-Module handelt, spezifiziert man dort
auch das Format der Ein- und Ausgabe.
Kann man zum Beispiel mit Grammatiken angeben oder heute moderner mit
XML, was auch nichts weiter ist als eine komplizierte Form, eine
Grammatik anzugeben.
Das war das.
Das waren also die Modul-Schnittstellen.
Und dann, wenn ich da weiterklicken kann, gibt es auch wieder die
Benutz -Relation, die ist genau gleich, wie ich schon beschrieben
hatte.
Und schließlich der Fein-Entwurf, auch nochmal als etwas Ausführlicher
beschrieben.
Man schreibt also die modulinternen Datenstrukturen und Algorithmen.
Das ist eigentlich wirklich nur eine Wiederholung.
Und Pseudocode haben wir auch schon besprochen.
Das war also der Überblick.
Jetzt ist natürlich die Frage, und was ist jetzt das Modul in dem
sogenannten modularen Entwurf?
Und dazu sind jetzt zwei Definitionen wichtig.
Die bitte ich wieder auswendig zu lernen.
Was ist ein Modul?
Naja, es ist einfach eine Menge von Programmelementen, die nach dem
Geheimnisprinzip gemeinsam entworfen und geändert werden.
Gemeinsam entworfen und geändert werden, heißt, sie gehören zusammen.
Und das Geheimnisprinzip steht auf der nächsten Folie.
Das sagt also, jedes Modul verbirgt eine, manchmal auch mehrere
Entwurfsentscheidungen hinter einer wohldefinierten Schnittstelle.
Wobei man darauf achtet, dass diese Schnittstelle sich bei
Veränderungen dieser Entwurfsentscheidung nicht mitändert.
Also, jetzt muss man erst zwei Dinge erklären.
Entwurfsentscheidung, was ist das eigentlich?
Naja, eine Entwurfsentscheidung ist das, wo Sie sich im Internen des
Moduls entscheiden, wie ein bestimmter Algorithmus abläuft, wie eine
Datenstruktur aufgebaut ist, welche Elemente Sie brauchen, wie die
Initialisierung funktioniert und so weiter.
Diese Entscheidungen, die treffen Sie nicht.
Sie können mal eine verlinkte Liste nehmen, ein anderes Mal ein Array,
ein anderes Mal eine Hash-Tabelle, dann irgendeine Mengenstruktur,
mehrere davon, Beschleunigungsalgorithmen mit einbauen.
Aber die Erfahrung mit der Softwareentwicklung ist immer, dass sich
diese Entwurfsentscheidungen über den Lauf des Systems, über die
Entwicklungszeit des Systems ändern kann.
Meistens sogar ändert und genau dann, wenn sie schwierig zu treffen
war, dann haben Sie sie vielleicht nicht optimal gewählt.
Und jetzt möchten wir das natürlich so haben, dass, wenn Sie diese
Entscheidungen revidieren müssen, einen anderen Algorithmus nehmen,
andere Datenstruktur, andere Ein- und Ausgabeformate, dass die anderen
Programmierer, die ja die andere Modulprogrammierung und auf ihr Modul
zugreifen, ihren Code nicht neu anpassen müssen.
Das heißt, die Schnittstelle sollte so sein, dass, was immer intern im
Modul passiert und geändert wird, niemand merkt was davon, außer dass
es vielleicht schneller läuft oder weniger Speicher braucht oder
einfacher ist oder so etwas.
Aber die benutzenden Programme werden die Schnittstelle immer so
vorfinden.
Das ist die Kunst des Entwerfers, dass wir eine Schnittstelle so
gestalten, dass die Nutzer gewissermaßen für die Implementierung des
Moduls maximale Flexibilität haben, aber niemanden behelligen, der
sich bereits auf ihr Modul hin programmiert hat.
Stellen Sie sich vor, wir nehmen irgendeine Klasse aus der Java
-Laufzeit -Bibliothek, eine grundlegende Klasse, eine AWT-Klasse oder
eine Language-Klasse oder eine Containerdatenstruktur und der
Hersteller, in diesem Falle Oracle, würde sagen, von einem Tag auf den
anderen.
Aber die Schnittstelle gilt nicht mehr.
Die müssen wir leider anders machen, weil das funktioniert nicht so
recht.
Das ist uns besser eingefallen.
Das läuft jetzt viel schneller.
Aber ihr armen Leute, ihr müsst jetzt leider alle eure Software
umschreiben, damit ihr mit dieser neuen Bibliothek zurechtkommt.
Da wäre dir zu Recht etwas umgehalten.
Die ganze Welt müsste sich dann umstellen und Software aktualisieren
und wer weiß, vielleicht hat man den Quellcode gar nicht mehr.
Man hat nur noch die Klassen.
Man kann gar nicht.
Darum ist es kritisch, äußerst kritisch, dass häufig benutzte Module
absolut stabile Schnittstellen haben, die sich nicht ändern, egal was
passiert.
Das sollte also selten sein und nur wenn eben das, was diese
Entscheidung sich nicht auf die Schnittstelle auswirkt, können Sie
sicher sein, dass Sie sie ändern können.
Das ist das Geheimnisprinzip, das also ganz wichtig ist für den
Entwurf.
Sie haben ja dann auch mehrere Beispiele gesehen.
So weit zur Erinnerung.
Dann geht es weiter mit Folie 54.
Das hier zum Beispiel ist die Containerdatenstruktur aus dem AWT, also
aus der Grafischen Benutzerbibliothek.
Da können Sie zu einem Container, der enthält Komponenten, die
angezeigt werden können, und Sie können eine Komponente hinzufügen mit
der Funktion Add.
Wir können einfach hinten anhängen oder eine bestimmte Stelle stellen
mit der Angabe, Sie können eine Komponente I mit ihrem Index in dieser
Schlange abfragen, wieder herausholen, also die Daten dafür bekommen.
Sie können sie auch über Koordinaten abfragen, das ist nicht so
wichtig.
Oder Sie können gleich alle herausholen und dann bekommen Sie ein Feld
davon und Sie können sie einzeln entfernen.
Sie können die Komponente selbst angeben, dann muss natürlich die
Implementierung von RemoveComponent durch die Liste hindurchgehen und
entfernen, die entsprechende Komponente finden und aus der Liste
entfernen.
Oder Sie geben den Index an und dann muss ich wieder durch das Feld
durchgehen.
Das ist unter der Annahme, dass dieser Container, der diese
Komponenten speichert, eine Liste hat.
Aber wer sagt, dass das eine Liste ist?
Könnte auch ein Feld sein.
Könnte eine doppelt verkettete Liste sein.
Könnte sogar eine Hashtabelle sein, in der Sie mit dem Index
indizieren.
Wichtig ist aber gerade in diesem Fall des Containers, dass die
Komponenten alle eine Reihenfolge haben, denn die Anordner, die die
einzelnen grafischen Komponenten auf dem Bildschirm anordnen, müssen
wissen, was zuerst kommt und was als zweites.
Das ist also eine solche Schnittstelle, die erlaubt es Ihnen zum
Beispiel, einen ganz einfachen Literator zu schreiben, der über die
Komponenten durchläuft, indem man einfach dieses I um 1 erhöht und
irgendwann, wenn man nichts mehr zurückkommt, sagt er, okay, jetzt
habe ich wohl das Ende erreicht, ich bin fertig.
So, jetzt.
Das war zur Erinnerung an dieses Beispiel.
65.
Sollte ich weitermachen?
Ja, genau, da zeige ich Ihnen noch dieses Bild.
In den traditionellen Programmiersprachen, um die Sie wahrscheinlich
in Ihrem professionellen Leben nicht herumkommen werden.
Die Welt wird nicht immer nur Java von Ihnen verlangen, sondern die
Welt wird von Ihnen auch C und C++ verlangen und vielleicht auch noch
JavaScript und eine ganze halbe Dutzend anderer Programmiersprachen.
Ich gehe jetzt einfach mal nur kurz darauf ein, wie das in C zum
Beispiel gemacht wurde.
Würde, Sie wissen ja, wir haben bei diesem Quick Index mehrere Module.
Eines davon ist der Zahlenspeicher, dafür gibt es eine Implementierung
und eine Schnittstellenbeschreibung, das ist eine sogenannte .h-Datei
oder auch auf Englisch die Header-Datei.
Die Header-Datei ist sowas wie eine Schnittstelle in Java, gibt Ihnen
also den Namen der Methoden plus deren Signatur, also deren
Parameterliste mit den Typen und falls es Variablen gibt, natürlich
auch die Variablen mit ihren Typen und so weiter.
Das steht in diesen Header-Dateien, nichts anderes als eine
Spezifikation, hat nur einen anderen Namen, wird bei der Übersetzung
der Implementierung mit eingelesen über eine sogenannte Include
-Direktive und das ist so für alle anderen.
Die zum Beispiel, diese Implementierung implementiert die
Schnittstelle der Eingabe und um sie zu implementieren, braucht es
natürlich auch die Schnittstelle, die der Zahlenspeicher zur Verfügung
stellt, also inkludiert die Implementierung auch diese andere
Schnittstelle und die bauen sich mehr und mehr auf.
Da gibt es den Verschieber, der braucht ebenfalls den Zahlenspeicher
-Schnittstelle, die braucht er nicht und der Sortierer, der braucht
ebenfalls die Schnittstelle des Verschiebers und schließlich die
Ausgabe braucht die Schnittstelle des Verschiebers, der gibt Ihnen
immer die nächste Verschiebung mit an.
So ist das also in einer Programmiersprache wie C oder auch C++
gebaut.
Es gibt auch andere Programmiersprachen, die heute nicht mehr in
Gebrauch sind.
Da gibt es eine Programmiersprache, die ist modular, wie der Name
schon sagt, ist die gebaut für eine Modularisierung nach diesem
Muster, etwas moderner als C und C++ ist aber heute kaum in Gebrauch.
Da gibt es jetzt aber Unterschiede, nämlich es ist nicht möglich, so
eine Schnittstelle wie in Java zum Beispiel, da können Sie eine
Schnittstelle hernehmen und sie mehrfach implementieren und damit
Varianten eines Programmes erzeugen.
Sie können eine schnelle Implementierung, eine langsame
Implementierung, eine für eine bestimmte Plattform, also einen
bestimmten Rechner implementieren und eine für einen anderen Rechner.
Das geht in C nicht so.
Da haben Sie immer nur zu jeder Schnittstelle maximal eine
Implementierung.
Sie können keine gleichzeitigen Implementierungen haben.
Es gibt also immer nur ein paar.
Wenn Sie mehrere Implementierungen haben wollten, dann müssen Sie
extra Kopfstände machen.
Es geht nämlich nicht so, dass Sie sagen, ich implementiere die
Schnittstelle neu mit einem neuen Namen, sondern müssen einfach sich
was ausdenken, mit der Sie mehrere Implementierungen in ein Modul
reinquetschen und dann irgendwie dynamisch entscheiden, welche der
gegebenen Funktionen Sie wählen.
Und wie gesagt, das erkläre ich jetzt nicht.
Das ging jetzt zu weit.
Das ist ja keine C-Vorlesung, die wir hier haben.
Aber da muss man ein bisschen arbeiten.
Im Vergleich dazu bei den objektorientierten Sprachen können Sie die
Schnittstelle mehrfach implementieren.
Auch abstrakte Klassen können Sie mehrfach implementieren.
Davon können Sie immer auch mehrere Objekte erzeugen.
Selbst wenn Sie nur eine Implementierung haben, können Sie mehrere
Kopien haben, eine Instanz.
Auch das geht in C nicht.
In C++ dann natürlich schon.
Sie können natürlich umgekehrt es auch erreichen, dass es nur eine
Implementierung gibt, indem Sie alle Datenstrukturen und Funktionen,
Methoden in statische Attribute von Klassen implementieren.
Mit den OO-Sprachen ist es dann auch möglich, den Methodennamen
mehrmals zu verwenden, solange er in einer anderen Implementierung
ist.
Zum Beispiel das hohle Zeichen oder VS-hohle Zeichen in C brauche ich
zwei verschiedene Namen für die zwei verschiedenen Implementierungen
von eigentlich der gleichen Schnittstelle.
Und das muss ich in Java zum Beispiel nicht machen.
Ich kann das beide Male hohle Zeichen verwenden, weil ich immer den
Objektnamen oder Klassennamen davor setzen muss.
Damit ist es auf die Art und Weise entzerrt.
Das waren jetzt diese Punkte für die Modularisierung.
Was dann jetzt noch fehlt, ist die Benutzrelation.
Die Benutzrelation ist nur eine einzige von vielen möglichen
Relationen, die in Programmen stecken.
Relationen können Sie ja gerade bei objektorientierten
Programmiersprachen die Assoziationen genau angeben.
Aber selbst wenn Sie keine angeben, die, die hier entstehen, kommen
fast immer vor.
Zum Beispiel Delegierten an oder Weiterreichen an.
Sie kriegen einen Aufruf, Sie reichen ihn aber einfach nahezu
unverändert an eine andere Funktion weiter.
Dann haben Sie die Ist-ein-Hierarchie, das ist die Klassen-Hierarchie,
die Vererbungs-Hierarchie.
Hat-ein, das heißt enthält etwas.
Das gleiche, das enthält etwas.
Oder hat-ein ist eigentlich das gleiche.
Dann greift auf etwas zu, ist wieder eine Relation.
Ist privilegiert zu, also darf etwas benutzen oder besitzt etwas, ist
wieder eine Relation.
Dann die wohlbekannte Aufrufs-Relation.
Da gibt es dann eben die Aufrufs-Hierarchie oder die Inverse davon,
aufgerufen von.
Und schließlich die Benutz-Relation, die wir jetzt genau ansehen, und
stellt Betriebsmittel, ist noch eine Relation.
Wir schauen uns jetzt die Benutz-Relation an.
Wir sind noch im Modulan-Entwurf.
Also die Benutz-Relation.
Man sagt folgendes, Programmkomponent A benutzt Programmkomponent B,
wenn A für den korrekten Ablauf die Verfügbarkeit einer korrekten
Implementierung von B erfordert.
Also es muss eine Implementierung von B da sein.
Das heißt übrigens, wenn Sie genau hinschauen, das ist nicht so
formuliert, dass A B aufruft.
Das ist eine mögliche Weise, B zu benutzen, aber nicht die einzige.
Ich gebe Ihnen gleich Beispiele dafür, wie ich B benutzen kann, ohne
dass ich es explizit aufrufe.
Also Verfügbarkeit erfordert, die Verfügbarkeit einer korrekten
Implementierung gegeben ist.
Verfügbarkeit heißt, A delegiert nach B.
A bekommt einen Aufruf und leitet ihn weiter an die Komponente B, weil
es selbst die Funktion nicht ausführen kann.
Oder A greift auf Variablen von B zu.
B ist zum Beispiel ein Modul, da ist eine Variable drin, die ist
öffentlich und auf die greift A zu.
Das wäre eine Möglichkeit.
A ruft B auf, das hatten wir schon.
Das ist in der Regel eine Benutzrelation vorausgesetzt.
Der Ablauf von B ist auch tatsächlich für den korrekten Ablauf von A
erforderlich.
Das ist nicht immer der Fall.
Zum Beispiel, wenn Sie ein Programm starten auf Ihrem Rechner, dann
ist es im Wesentlichen so, dass das Betriebssystem einen Aufruf an
Main ablegt.
Es führt also einen Aufruf aus.
Ist das Betriebssystem in seiner Funktionsweise auf Ihre Anwendung B
angewiesen?
Nein, überhaupt nicht.
Sie wollen ja, dass das Betriebssystem, selbst wenn Ihre Anwendung B
nicht funktioniert, zusammenbricht.
Dass das Betriebssystem sagt, ich bin immer noch da.
Ich bin immer noch da, was sollen wir jetzt machen?
Nächster Aufruf.
Das heißt also, das Betriebssystem ruft zwar Ihr Main oder Ihr B auf,
erfordert aber nicht die korrekte Verfügbarkeit davon.
Es ist davon oberhin.
Es ist nur ein Dienst, den es anbietet.
Und wenn das Programm crasht, dann ist es halt so.
Für das Betriebssystem.
Es benutzt also das Betriebssystem nicht eine Anwendung.
A legt eine Instanz eines Typs von B an.
Na klar, dazu brauche ich auch das B, sonst geht das nicht.
Das wäre eine Benutzung.
Und ein weiterer indirekter Aufruf wäre die Ansteuerung über eine
Unterbrechung.
Das muss ich vielleicht genauer erklären.
Also stellen Sie sich vor, A ist jetzt Ihr Programm und B ist die
Dateiverwaltung Ihres Betriebssystems.
Das läuft dazu ab, wenn Sie jetzt zum Beispiel eine Datei öffnen
wollen, dann rufen Sie natürlich eine Funktion, die heißt dann Open
oder so etwas, auf.
Dieser Aufruf ist aber kein wirklicher Aufruf, sondern der setzt eine
sogenannte Unterbrechung.
Das ist ein sogenannter Systemaufruf.
Der setzt eine Unterbrechung, ein Bit in einem Register des
Betriebssystems.
Das ist die Implementierung von Open.
Und das ist sorgfältig festgelegt, welches Bit das ist.
Diese Unterbrechung bewirkt, dass das Betriebssystem sagt, stopp, ich
habe eine Unterbrechung bekommen.
Das laufende Programm wird angehalten.
Ich sehe mal nach, welches Bit da gesetzt wurde und lasse dann das
zugehörige Programm, nämlich für das Öffnen der Datei laufen.
Es geht sogar noch weiter.
Also Sie bekommen hier einen indirekten Aufruf in das Betriebssystem
herein.
Sie sagen nicht Open aufrufen, sondern Bit setzen.
Betriebssystem schaut nach, was muss ich denn eigentlich machen.
Es geht aber noch weiter.
Wir nehmen noch ein weiteres Beispiel angenommen, Sie wollen nicht nur
die Datei öffnen, sondern die Datei auch lesen.
Das heißt, Sie müssen einen weiteren Systemaufruf ansetzen, der sagt,
bitte ein Kilobyte oder vier Kilobyte von der Platte lesen,
Hauptspeicher befördern und mir davon das erste Byte geben.
Das ist ein typischer Lesebefehl.
Was macht das?
Es legt wieder einen Systemaufruf ab, einen indirekten Aufruf an das
Betriebssystem.
Das Betriebssystem steuert die Platte an, sodass die Platte ihre Daten
liefert.
Aber das ist ja elend langsam.
Es dauert Millisekunden, bis der Kopf positioniert ist und die Sektor
der Platte angekommen ist und im Kopf und das Ding abgelesen ist.
Also will das Betriebssystem sagen, solange du mit diesem Transfer der
Daten beschäftigt bist, kann ich ein anderes Programm laufen lassen.
Mach das dann auch.
Jetzt kommt der zweite Fall, wo ein indirekter Aufruf passiert.
Diesmal nicht mal von Ihrem Programm, sondern die Steuerung der Platte
muss jetzt die Aufmerksamkeit des Betriebssystems erregen und sagt,
ich bin mit dem Transfer fertig und ruft in diesem Fall ebenfalls über
eine Unterbrechung die untere Hälfte der Leseoperation ab.
Die obere Hälfte sagt, Platte ansteuern, weggehen, was anderes machen.
Und die Platte dann sagt, ich habe es geschafft, ich habe die
Millisekunde nützlich gesetzt, die hat in der Zwischenzeit zwei
Millionen Instruktionen ausgeführt, aber ich habe es jetzt übertragen,
dann sagt sie noch mal eine Unterbrechung und jetzt kommt die
Behandlungsroutine dieser Unterbrechung ins Spiel, die sagt, okay, gib
mir die Daten her, ich transferiere sie in den Bereich der Anwendung
und wecke dann die Anwendung wieder auf.
Sie sehen also, ich habe hier eine ganze Kette von Dingen, die benutzt
werden, ohne dass ich sie aufrufe.
Aber sie sind erforderlich für den korrekten Ablauf ihres
Anwendungsprogramms, obwohl sie sie nur zum Teil indirekt aufgerufen
haben.
Also das zur benutzten Relation, damit sie besser verstehen, was das
ist.
Wir haben also den Fall, dass Aufrufe indirekt sein können, Aufrufe
ausgelöst werden können von ganz anderer Software, die sich
zurückwirken auf ihr Programm.
Denken Sie an die Abschlussoperation für die Datenübertragung.
Und es kann sein, dass sie zwar etwas aufrufen, aber gar nicht davon
abhängig sind, dass das richtig läuft.
Es ist einfach nur ein Dienst, den sie anstoßen auf Wunsch und wenn
der nicht funktioniert, ist es ihnen auch egal.
Dazu werden wir auch noch ein Beispiel haben.
Okay, also mein Petitum ist nur, dass ihnen klar ist, dass wenn A B
aufruft, es in der Regel so ist, dass A auch B benutzt, aber nicht
unbedingt so sein muss, siehe Betriebssystem.
Siehe auch, wie wir später sehen werden, Rückrufe.
Und ferner, dass es Aufrufe geben kann, die nicht offensichtlich sind
und trotzdem eine Benutzung stattfindet.
Wir möchten jetzt dafür sorgen, dass die Benutzrelation eine
Halbordnung ist.
Das heißt, die Relation selbst enthält keine Zyklen.
Sie kann baumartig sein, sie kann sogar grafartig sein, dass mehrere
Dinge ein gemeinsames Teil benutzen, also von da nach da über mehrere
Zwischenstufen auf eine gemeinsame Komponente benutzen.
Aber wir möchten nicht, dass sie zyklisch ist.
Nun gibt es ein Paradebeispiel für nicht-zyklische Systemstrukturen,
das ist die sogenannte Schichtenarchitektur.
Das Layered System, die Schichtenarchitektur.
Die sieht so aus, wenn wir mal ein Bild haben.
Zur Schichtenarchitektur kommen wir erst später.
Aber eines der ersten Systeme, die tatsächlich mit der
Schichtenarchitektur aufgebaut waren, war ein Betriebssystem, das THE.
Betriebssystem, müssen Sie jetzt nicht studieren, ist uralt, das war
in den 70er Jahren, oder wenn nicht sogar 60er Jahren.
Dann das Betriebssystem Maltex, das war ein Vorläufer von Unix.
Und das Unix ist natürlich auch schichtenorientiert.
Ich werde Ihnen später ein paar Schichten geben.
Dann die Protokollsuiten, um im Internet Daten zu übertragen, ISO-OSI
oder CCP-IP-Protokollsuite.
Dann die sogenannte Drei-Schichten-Architektur, die immer wieder
vorkommt, die sagt, ich habe eine Benutzungsschnittstelle, das ist
Schicht Nummer 1, das ist die oberste.
Ich habe einen Applikationskern, der rechnet, der rechnet die
Anwendung aus.
Und eine dritte, untere Schicht, die unterste Schicht, das ist die
Datenhaltung, in der werden die Daten gespeichert und persistent
gehalten, sicher und dafür gesorgt, dass sie auch bei Stromausfall und
dergleichen nicht verloren gehen.
Eine Datenbank, wie kann das?
Das ist eine typische Drei-Schichten-Architektur, wir sind aber
keineswegs gebunden an nur drei.
Aber dieses Muster ist sehr häufig.
Wann sollen wir unsere Benutzungsrelation so machen, dass A, B
benutzt?
Also das Erste ist natürlich klar, wenn A, B benutzen darf oder soll,
dann sollte es dadurch einfacher werden, dass es was benutzt, das hier
drin vorkommt und ich es nicht nochmal implementieren muss.
Das ist ja offensichtlich.
Wir wollen aber auch nicht, dass das B das A benutzt nach Möglichkeit,
also keine zyklischen Abhängigkeiten.
Das heißt also, wenn B das A nicht benutzen darf, dann kann das ein
Nachteil sein für B.
Aber das sollte nicht wirklich so sein, dass B dadurch, dass das A
nicht benutzen darf, sollte nicht wesentlich komplizierter werden.
Wenn das A aber das B benutzen will und das B umgekehrt das A, dann
ist was anders falsch, dann ist irgendwie möglicherweise so, dass das
A und B sowieso zusammengefasst werden sollten oder aber eine andere
Strukturierungsmöglichkeit gesucht werden soll, damit diese
Abhängigkeit nicht mehr besteht, diese doppelte Abhängigkeit,
zyklische.
Es gibt mindestens eine nützliche Untermenge, die B aber nicht A
enthält.
Das heißt also, gibt es vielleicht noch einen anderen Bestandteil
meiner Komponenten oder ein ganz anderes System, das B ebenfalls
mitbenutzen kann, dann rentiert es sich, dass ich das eine einzelne
Komponente mache, die mehrfach benutzt wird.
Eine nützliche Untermenge, die B enthält, aber ohne das A.
Dann muss ich die beiden trennen, denn sonst könnte ich natürlich auch
hergehen und A und B zusammen klatschen, aber dann habe ich
möglicherweise die Logik von A mit dabei, die ich ein andermal gar
nicht brauche.
Und es gibt keine Untermenge, die zwar A enthält, aber das B dann
nicht benutzt wird.
Das wäre auch sinnlos.
Ich will das A benutzen, aber ich habe nur solche Aufrufe, die nicht
zu einem Aufruf von B führen.
Das wollen wir nicht, sondern wenn A, dann muss auch B benutzt werden.
Immer.
Also, wir wollen A vereinfachen, dadurch, dass B benutzt darf.
Den Code muss ich nicht duplizieren.
Das B wird nicht unglaublich komplex oder muss deswegen das A oder
Teile des A nichts Neues implementieren, um von A benutzt werden zu
können.
Es gibt eine Untermenge, in der B vorkommt, aber das A gar nicht da
ist.
Dann habe ich eine Komponente, die mehrfach benutzt werden kann und
das A, das B immer braucht.
Das ist die Idee, so machen wir es.
Und das sagte ich schon, wenn die Benutzrelation dazu gebaut ist, dass
die Zyklin frei ist, dann ist es ja erstens eine Halbordnung oder eine
Hierarchie.
So, und jetzt sage ich Ihnen kurz, warum ich das mit den Zyklin nicht
haben will.
Also stellen wir uns vor, wir haben so eine Struktur, das A benutzt
das C, das D benutzt ebenfalls das C, das C benutzt das E und das F,
das B benutzt das C, es geht also irgendwie quer durch die Gegend und
hier unten haben wir noch eine Nutzung von F nach B.
Das hat jetzt den folgenden Effekt.
Ich kann F nicht für sich testen, denn F benutzt ja B.
Also muss ich zum Testen, wenn ich F geschrieben habe, auch schon eine
Implementierung von B haben und das B braucht dann auch natürlich noch
gleich die Implementierung von E und C.
Also habe ich so eine Katze beißt sich in den Schwanz Situation.
Ich kann auch das B nicht für sich testen, denn es benutzt schließlich
das F.
Aber schick wäre es, wenn diese Abhängigkeit, diese Benutzrelation
nicht vorhanden wäre, denn dann kann ich das F nehmen und für sich
testen.
Von unten her kann ich hoch testen, als nächstes würde ich dann das E
nehmen, das E testen und dann das geht, weil das F ist fertig, ist
getestet.
Und schließlich das B, das kann jetzt das E und das F und natürlich
auch das C mit benutzen und somit sind wir fein raus und schließlich
kann ich auch noch das A und das D testen.
Jedes Mal wird das System größer, aber ich kann anfangen, das System
inkrementell zu testen.
Es ist so offensichtlich, aber glauben Sie mir, am Anfang hat man das
nicht verstanden.
Man nannte das eben dann den Big Bang Test.
Big Bang Test heißt, ich konnte nichts testen, weil alles irgendwie
zusammenhängt.
Alles benutzt irgendwo eine Kontrollstruktur, Kontrollblock oder Daten
von irgendwo anders her.
Also musste ich alles einzeln programmieren lassen, ein ganzes
Betriebssystem oder ein gutes Teil davon und dann haben wir irgendwann
mal den Test.
Dann wird alles drauf geladen und das heißt dann Big Bang, weil alles
wirklich explodiert.
Es läuft einfach gar nichts.
Das nennt man auch den Effekt, es läuft gar nichts, bis alles läuft.
Nothing works until everything works.
Und das will man unbedingt vermeiden, weil die Fehlersuche dann so
unglaublich schwierig wird und den ganzen Testzyklus verzögert.
Ich muss warten, bis das B und das E fertig ist, bevor ich das F
testen kann.
Und das ist sehr, sehr ungeschickt.
Und wenn es kracht, dann weiß ich ja nicht wo.
Wo liegt jetzt der Fehler?
Liegt der in B, in E oder in F?
Und wenn es aus noch größeren Komponenten sind, dann sind sie dran und
können nichts finden.
Also das sollte man vermeiden.
Deswegen lege ich so großen Wert darauf, dass die Benutzrelation
wirklich frei ist.
Noch ein Punkt zur Benutzrelation.
Und zwar der berühmte Rückruf.
Der berühmte Rückruf verursacht nämlich einen Zyklus in der
Benutzrelation.
Jedenfalls vermeintlich.
Also stellen Sie sich Folgendes vor.
Sie haben hier einen Anwendungskern, darunter meinetwegen auch noch
die Datenhaltung, interessiert uns aber jetzt nicht.
Darüber eine Benutzerschnittstelle.
Und wenn Sie schon mal eine Benutzerschnittstelle implementiert haben
oder programmiert haben, dann müssen Sie die sogenannten Callbacks
registrieren.
Die Callbacks heißen, Sie sagen dem Anwendungskern, dass unter
bestimmten Umständen eine Funktion oder mehrere in der
Benutzerschnittstelle aufgerufen werden müssen.
Zum Beispiel, wenn der Anwendungskern eine Datenstruktur, die
angezeigt wird, in der Benutzerschnittstelle verändert und damit die
Benutzerschnittstelle nochmal neu gezeichnet werden muss.
Dann gibt es hier einen Aufruf an Redraw.
Aber diese Funktion ist hier drin.
Und jetzt sehen Sie natürlich, wenn man sich das erstmal so anschaut,
ist offensichtlich ein Zyklus da.
Nämlich die Benutzerschnittstelle benutzt natürlich den
Anwendungskern, sonst könnte sie ja gar nichts machen.
Jedes Mal, wenn Sie auf einen Knopf drücken, bewirkt das, dass eine
Anwendungskernfunktion aufgerufen wird, wenn wieder Daten reingegeben
werden oder Daten rausgeholt werden.
Und umgekehrt haben Sie einen Aufruf jetzt, dieses Callbacks oder
Rückrufs an die Benutzerschnittstelle.
Schon hupen wir alle Warnsignale, wir haben einen Zyklus, wir können
nicht einzeln testen und so weiter.
Aber ist der Anwendungskern tatsächlich von der Benutzerschnittstelle
abhängig?
Und die Antwort darauf ist nein, eigentlich nicht.
Er ruft zwar die Benutzerschnittstelle an und sagt, mach was du machen
musst.
Mich interessiert das aber gar nicht.
Mein Anwendungskern ist weiterhin konsistent, meine Daten sind
vorhanden, sie sind richtig und wenn die Benutzerschnittstelle
irgendwelchen Blödsinn macht, ist mir das gerade egal.
Das heißt, der Anwendungskern ist nicht abhängig, benutzt eigentlich
die Benutzerschnittstelle nicht, obwohl ein Rückruf anstößt.
Das ist auch wieder so ein Fall, wo man zwar einen Aufruf hat, aber es
streng genommen keine Benutzung ist.
Der Anwendungskern lebt für sich.
Er kann auch ohne Benutzerschnittstelle zurechtkommen.
Dadurch haben wir hier keine Benutztradition.
Ist nicht abhängig von einer Verfügbarkeit der Benutzerschnittstelle.
In der Tat, sie könnten wahrscheinlich die Benutzerschnittstelle
austauschen.
Und der Anwendungskern könnte gleich bleiben.
Das zu dieser Geschichte.
Und das kommt halt gerade in den Benutzerschnittstellen die ganze Zeit
vor.
Zum Beispiel für jeden Knopf, der betätigt werden kann in der
Benutzerschnittstelle, müssen sie einen sogenannten Mauslistener
registrieren.
Der Mauslistener ist eine Instanz einer bestimmten Schnittstelle.
Der hat mehrere Funktionen, Maus up, Maus down, klickt und so weiter.
Der kann die ganzen Funktionen, die sie mit der Maus durchführen
können, abfangen, reagieren darauf.
Aber der Aufruf auf diese Dinge, natürlich steht der Mauslistener in
ihrer Benutzerschnittstelle, aber wird nicht von ihnen aufgerufen,
sondern von dem unterliegenden Warteschlangensystem, das die
Ereignisse am Bildschirm aufsammelt und bearbeitet.
Auch das werden wir noch genauer sehen, wie eine Benutzerschnittstelle
im Implementierungskapitel nach dem Entwurf, wie so ein
Benutzerschnittstellenapparat wirklich funktioniert.
Was passiert, wenn sie eine Maustaste drücken?
Eine Maustaste drücken.
Das werden wir noch genauer sehen, aber nur mal jetzt im Vorgriff.
Achtung, beim Aufruf muss nicht unbedingt eine Benutzerrelation da
sein.
So, das habe ich jetzt besprochen.
Zum bisschen Geschichte sollte man auch noch wissen als Informatiker,
wer kommt das alles her, was hat sich das denn alles ausgedacht?
Ein gewisser David Parnas hat sich das ausgedacht, nämlich sowohl das
Geheimnisprinzip, als auch das Konzept des Moduls, als auch die
Benutzrelation einer ganzen Reihe weiterer Dinge.
Und ich möchte Sie jetzt bitten, dass Sie einen Artikel von ihm lesen,
den können Sie im Internet finden, der heißt Designing Software for
Ease of Extension and Contraction.
Also Software entwerfen, sodass sie leicht erweitert und verkleinert
oder kontrahiert werden kann.
Da erklärt er an einem länglicheren Beispiel, wie man das erreichen
kann, durch eine geschickte Wahl der Schnittstellen, sodass
Komponenten entstehen, die man vielfältig einsetzen kann, in
unterschiedlichen Systemen, großen und kleinen.
Ich muss Sie aber gleich warnen, es ist nicht ganz einfach zu lesen.
Aber es ist jedenfalls einfacher zu lesen als der hier von 1972.
And the criteria to be used in decomposing systems into modules müssen
Sie nicht lesen, aber wenn Sie wollen, lesen Sie es ruhig.
Es ist spannend, diese Originalartikel zu sehen und zu begreifen, was
die Leute sich damals gedacht haben und dass es jetzt noch Gültigkeit
hat, über 40 Jahre später.
Es gibt noch mehr, ein lustiger Artikel, es gibt auch einen Artikel
über Hierarchie und dass das Wort Hierarchie einfach überbenutzt wird,
aber es stört uns jetzt weniger.
The modular system of complex systems and a rational design process,
how and why to fake it.
Wir wissen ja alle, was fake heißt jetzt inzwischen, nach Fake News
und so weiter.
Why to fake it?
Damit meint er folgendes, dass ein Entwurf, wenn Sie einen Entwurf
gestalten, ist es ein kreativer Prozess.
Sie erfinden Komponenten, Sie erfinden Schnittstellen, Sie erfinden
Benutzungsrelationen und ein kreativer Prozess ist nie rational.
Er geht nicht Schritt A, Schritt B, Schritt C und so, sondern er geht
einfach in Sprüngen vor sich.
Da fällt Ihnen das ein, Ihr Kopf arbeitet ja fast ohne Ihr Zutun.
Dann fällt Ihnen plötzlich etwas ein oder Sie bemerken etwas und
sagen, ach nein, das müssen wir so und so machen.
Und das wollen Sie niemanden antun, der später dieses Design oder
diesen Entwurf verstehen will, sondern Sie wollen den Entwurf klar
beschreiben, so als wäre er in einem rationalen Prozess entstanden.
Auch ein interessanter Artikel, auch etwas, wenn Sie es interessiert
gerne lesen sollen, ist jetzt nicht prüfungsrelevant, natürlich, aber
finde ich sehr interessant.
Noch etwas, was er gemacht hat, ist eigentlich fast unerhört
heutzutage.
Er hat sich also vorgenommen, die Flugsoftware für ein Kampfflugzeug
nach seinen Prinzipien neben der Modularisierung mit dem
Geheimnisprinzip neu zu entwerfen.
Er ist also hergegangen und hat gesagt zum Verteidigungsministerium,
ich nehme mal eure Software her und restrukturiere sie, sodass man
besser damit zurechtkommt, dass sie eine klare Aufteilung der
Komponenten hat, sodass man Komponenten austauschen kann, wenn sie
genauer werden.
Da gehört also zum Beispiel ein Weltmodell dazu, ein Modell der
Erdkugel.
Wo ist denn der Äquator zum Beispiel?
Und die Weltkugel ist ja keine perfekte Kugel, sondern sieht eher aus
wie so eine ausgebeulte Birne.
Das müssen aber die Navigationssysteme verstehen.
Er hat also einen Artikel geschrieben, aufgrund der Spezifikation für
dieses Flugzeug.
Ich habe den Artikel mit Begeisterung gelesen und er hat am Schluss
geschrieben, man kann sich die Unterlagen dafür besorgen.
Man muss nur da und da hinschreiben.
Ich war damals schon in Karlsruhe und habe gesagt, das probiere ich.
Jetzt habe ich zu Hause eine Spezifikation des Flugzeuges A7E über die
Flugsoftware.
Die haben die mir einfach geschickt.
Stellen Sie sich das vor.
Könnte ja ein Russe gewesen sein, oder?
Das Einzige, was unschön war, ist, dass alles, was mit Waffen zu tun
hatte, geschwärzt war.
Alles, was mit Waffen zu tun hatte, war geschwärzt.
Ich habe also die ganze Software-Spezifikation der A7E, ein Flugzeug,
das inzwischen außer Dienst gestellt wurde, aber es war jedenfalls
damals ein spannender Jäger.
Okay, das auch nur nebenher.
Also ein bisschen was sollte man immer über die Geschichte wissen.
Dieser David Parnas lebt übrigens in Kanada, ist noch am Leben.
So, jetzt gehen wir zum objektorientierten Entwurf.
Aber bevor ich das tue, können Sie mir natürlich Fragen stellen.
Gibt es irgendwas, das nicht klar ist?
Alles geklärt.
Wir müssen hier gar nicht so viel Neues lernen, denn die Prinzipien
des modularen Entwurfs, nämlich das Geheimnisprinzip Flexibilisierung
durch änderungsunempfindliche Schnittstellen, bleibt weiterhin so und
das machen wir natürlich in der objektorientierten Geschichte schon
gewissermaßen implizit.
Wir denken kaum mehr darüber nach.
Und diese Schnittstellen verbergen also Entwurfsentscheidungen, was ja
nur ein anderes Wort ist dafür, dass sie Algorithmen und
Datenstrukturen festlegen.
Diese Entwurfsentscheidungen können revidiert werden, wenn die
Schnittstelle geschickt gewählt ist.
Es ändern sich eigentlich nur die Bezeichner.
Wir müssen nur die Analoga finden.
Wir haben jetzt kein Modul mehr.
Was ist das Modul in Java?
Nun, das kann entweder eine einzelne Klasse sein oder ein Paket.
Ein Paket ist typischerweise die Größe eines Moduls.
Ein Paket sind also die Klassen zusammengefasst, die eine oder mehrere
gemeinsame Entwurfsentscheidungen enthalten.
Eine einzelne Klasse kann ein Modul komplett verwirklichen, muss aber
nicht.
In der Regel ist es so, dass sie mehr als eine Klasse brauchen.
Der externe Entwurf, Sie erinnern sich, der externe Entwurf ist
anstelle des Modulführers, bekommen Sie jetzt ein Paket oder
Klassenführer, das heißt ein UML-Diagramm, ein UML-Klassen- oder UML
-Paket -Diagramm mit erläuternden Tests, der dokumentiert, das ist,
was der Modulführer für PANAS war.
Und die analogsten Mitschnittstellen sind natürlich die Schnittstellen
der Klassen, die öffentlichen, alle Dinge, die als öffentlich markiert
sind oder implizit öffentlich sind.
Also alle Methoden, Attribute, die eine Klasse hat und die
öffentlichen Klassen eines Pakets.
Ja, also das kann man gut verstehen.
Beim internen Entwurf, das sind ja jetzt, was passiert innen drin, die
Benutzreaktion wird jetzt auf der Ebene von Paketen angelegt und
vielleicht auch alleinstehende Klassen, wenn eine Klasse reicht, um
ein ganzes Modul zu implementieren.
Und schließlich, der feine Entwurf macht genau das gleiche wie vorher,
nämlich Implementierungsdetail, Datenstrukturen, Algorithmen liefern
in Pseudocode hoher forderlich, wie beim modularen Entwurf.
Okay, das passt also alles zusammen, nur ein paar Wörter sind ja
anders geworden.
Und deswegen finde ich es ja so interessant, dass die Konzepte von
PANES in neue Wörter gekleidet heute noch vorhanden sind.
Allerdings gibt es jetzt im UN-Entwurf zusätzliche Freiheitsgrade.
Sie können zum Beispiel Klassen mehrfach instanzieren.
Ich sagte Ihnen am Anfang, ein Modul, da gab es nur eine Schnittstelle
und die Implementierung und das war's.
Sie können jetzt eine Klasse hernehmen und davon mehrere Instanzen,
beliebig viele Instanzen sogar, bilden und Aufrufe machen.
Das gibt Variationsmöglichkeiten.
Sie haben Vererbung und Polymorphie und Sie können damit eine
sogenannte Variantenbildung in einem einzigen Programm erzeugen.
Also mit C tun Sie sich schwer, zwei Varianten eines Programms in
einem Code zu verwirklichen.
Sie müssen dann immer mehrere Module haben, einmal so implementiert,
einmal so implementiert, geht aber nicht in ein einzelnes Programm,
sondern Sie müssen einmal ein Paket zusammenladen, ein anderes Paket
zusammenladen.
Das ist ungeschickt, umständlich, das geht mit objektorientierten
Sprachen wie Java, aber auch C++, Smalltalk und so weiter, viel
eleganter.
Wie man das jetzt im Entwurf geschickt einsetzt, dazu gibt es eine
ganz neue Entwicklung, die sogenannten Entwurfsmuster oder Design
Patterns, die Ihnen zeigen, wie man das macht, wie man diese
Polymorphie geschickt zur Variantenbildung, unter anderem zur
Variantenbildung, aber auch zu anderen Zwecken einsetzt.
Es gibt objektorientierte Entwurfsmuster, wie auch klassische
Entwurfsmuster, die ohne OO auskommen, zum Beispiel die
Schichtenarchitektur ist eine klassische Entwurfsmuster ohne
Vererbung, da brauche ich keine Vererbung dazu, während Sie, wenn Sie
zum Beispiel MVC oder Beobachter nehmen, da brauche ich Vererbung
dazu.
Aber die Entwurfsmuster und etwas größere Entwurfsmuster nennt man
dann Architekturstile, wobei mir persönlich immer schwerfällt, da eine
genaue Unterscheidung zu finden, die schauen wir uns als nächstes an.
Objektorientierter Entwurf, auch einer der Wegbereiter, der auch
natürlich großen Einfluss nicht nur auf den Entwurf hatte, sondern
auch die Sprachentwicklung selbst, die objektorientiert, der Objekt
ist ein gewisser Grady Butch, der war Chief Scientist, Rational
Software Corporation, jetzt ist das glaube ich von IBM gekauft, also
ist es wahrscheinlich bei IBM, aber habe ich nicht so genau jetzt mehr
verfolgt.
Er hat ursprünglich mit der Programmiersprache Ada gearbeitet.
Ada ist eine Sprache aus den 80er Jahren, noch nicht objektorientiert,
kam aber schon nahe ran und hatte dann später, als Ada
objektorientiert wurde und Klassenbibliotheken dafür möglich waren,
sie in Büchern geschrieben.
Hat sehr großen Einfluss gehabt auf die Kunst des Entwurfs.
Also diese beiden Pioniernamen sollte man sich vielleicht merken, dass
man sie schon mal gehört hat.
So, jetzt gehen wir zu den Architekturstilen.
Also Architekturstile.
Noch was klicken, wunderbar und los geht's.
So, da kriegen Sie jetzt eine Reihe von Konzepten, die haben jetzt
noch nichts, wenig bis gar nichts mit Objektorientierung zu tun, aber
sind auch wichtig.
Ein zentraler Konzept ist die abstrakte Maschine, manchmal auch
virtuelle Maschine genannt.
Haben Sie natürlich schon gehört, die Java Virtual Machine, das ist
also etwas Bekanntes.
Da sagt man, eine abstrakte Maschine oder virtuelle Maschine ist eine
Menge von Softwarebefehlen und Objekten, die auf einer
darunterliegenden abstrakten oder realen Maschine aufbauen und diese
ganz oder teilweise verdecken können.
Was soll das?
Ich erkläre es mal Schritt für Schritt.
Ich erkläre es mal anhand der Java VM.
Die Java VM gibt Ihnen eine Menge von Befehlen, in die der Java
Übersetzer übersetzt.
Diese Befehle haben zum Beispiel auch einen Methodenaufruf.
Einer dieser Befehle ist ein Methodenaufruf, der sagt, gegeben Objekt,
gegeben Methodenname, suche ich den zugehörigen Methode und lasse sie
laufen.
Und Sie wissen, das ist bei Polymorphie nicht ganz einfach, nicht
unbedingt einfach, denn Sie müssen unter Umständen auf die Suche
gehen, welche Oberklasse hat denn jetzt eine Implementierung dieser
Methode.
Das ist ein ganz ordentlich mächtiger Befehl, Methodenaufruf.
Es hat natürlich auch Befehle wie eine Variable auf den Keller laden,
eine zweite Variable auf den Keller laden, die beiden Variablen
addieren, das ist ein dritter Befehl, wobei das Ergebnis wieder auf
den Keller abgelegt wird.
Und dann gibt es noch einen vierten Befehl, das was oben auf dem
Keller liegt, in den Hauptspeicher zu transferieren.
Diese virtuelle Maschine, diese Java virtuelle Maschine hat also
Befehle.
Nur es gibt keine echte Hardware Maschine, die diese Befehle kann.
Und diese Befehle müssen erneut umgesetzt werden in der Folge von
Instruktionen für eine gegebene Plattform, sodass sie tatsächlich das
ausführt.
Und das ist, was in der virtuellen Maschine drin steckt.
Also die Java virtuelle Maschine gibt dann erstens eine Menge von
Befehlen, auf die ein Compiler sich richten kann und eine Übersetzung
dieser Befehle und Implementierung dieser Befehle, sollte ich sagen,
in der entsprechenden Befehlssatz eines Rechners.
Das kann jetzt ein Intel Rechner sein, es kann ein IBM Rechner sein,
es kann ein ARM Rechner sein, es kann was weiß ich, welche Plattform
weiß ich.
Die Aufgabe des JVM Implementierers ist dann immer für eine neue
Plattform diese Befehle umzusetzen.
Also die Befehle sehen aus wie eine Maschine, sind ja Befehle.
Aber sie sind nicht reell, sie sind abstrakt, deswegen abstrakte
Maschine oder virtuelle Maschine.
Und das hat also das da zu tun und schließlich das mit dem Verdecken
einer drunterliegenden echten oder abstrakten Maschine ist jetzt auch
klar.
Das sind natürlich, solange sie mit der JVM arbeiten, haben sie keinen
Zugang zu den drunterliegenden Befehlen der echten Hardware.
JVM gibt ihnen keinen Zugang dazu, sie können nur die Befehle der JVM
benutzen.
Wenn Sie sich einen anderen Befehl ausdenken, sagt der JVM tut mir
leid, ich führe gar nichts aus, das ist ein Sicherheitsproblem, was du
hier machen willst.
Und es ist auch gut so, dass es vermieden wird, denn sonst könnte ich
alles mögliche an dummen Dingen anstellen, böse oder auch dumm, dumm
und böse, beides.
Indem ich Befehle der JVM ausführen lasse und gelegentlich einen
Assembler -Code dazwischen schiebe.
Der bringt mir dann den Keller durcheinander oder die Aufrufs
-Hierarchie oder überschreibt irgendetwas.
Das wäre natürlich erstens ein Einfallstor, aber auch würde dazu
führen, dass möglicherweise die Konsistenz der virtuellen Maschine
vernichtet wird.
Teilweise oder ganz, in diesem Fall ganz verdeckt.
Was gibt es da noch?
Programmiersprache an sich ist auch so eine virtuelle Maschine, die
gibt es ja gar nicht.
Wenn Sie in Java programmieren, es gibt keine einzige Maschine, von
der ich wüsste, dass sie Java in Quellcode interpretieren würde.
Wir müssen erst noch ein Programm schreiben, das diesen Quellcode
interpretiert, daher das Wort Interpretierer.
Und dann haben Sie tatsächlich eine virtuelle Maschine, die umsetzt
das um auf entweder JVM oder auch direkt auf die unterliegende
Plattform.
Es könnte ein Betriebssystem sein.
Betriebssystem gibt Ihnen eine Reihe von Kommandos.
Also wenn wir es mal von der grafischen Benutzeroberfläche absehen,
machen wir es uns erstmal einfach.
Betrachten wir einfach nur das Kommando-Fenster.
Da können Sie Dinge machen, wie Dateien herumschieben, umbenennen,
löschen, Programme aufrufen, Editoren aufrufen.
Das heißt, das Betriebssystem gibt Ihnen auch Befehle zur Verfügung,
die stehen in Ihrer Kommandosprache, in der Shell.
Das sind Befehle, die sind aber in Software implementiert, mithilfe
der darunterliegenden Plattform.
Das ist also auch eine virtuelle Maschine, und zwar eine sehr, sehr
mächtige.
Sie ist sehr, sehr viel mächtiger als ein Addierbefehl.
Sie kann die ganzen Dateien verwalten, kann Prozesse anlegen, kann die
Prozesse abwechselnd auf dem Prozessor laufen lassen, kann Dateien
lesen, kann kommunizieren mit anderen Rechnern.
All das müssen Sie sich nicht darum kümmern, die virtuelle Maschine
kann das schon.
Das ist also erst einmal von der Benutzerseite gesehen, aber auch für
den Programmierer liefert die Betriebssystemseite eine Menge von
Systemaufrufen, die, sag ich mal, ein langes Studium brauchen, bis man
eine Betriebssystemvirtuelle Maschine als Programmierer wirklich
beherrscht, dass man alle Kommandos kennt.
Ein Anwendungskern einer Anwendung ebenfalls, der bietet eine Anzahl
von Befehlen, die die Benutzerschnittstelle ansprechen kann, um eine
Datenbankabfrage zu machen und so fort.
Das ist auch eine GUI-Bibliothek, bietet Ihnen Komponenten, um
grafische Oberflächen zusammenzustellen mit Fenstern, mit Knöpfen, mit
Radioknöpfen, mit Rollbalken, ich weiß nicht, was es da alles gibt,
Fortschrittsbalken, Dutzende von Komponenten.
Wir müssen das nicht in Assembler programmieren oder in C oder in
Java, sondern das ist alles schon fertig.
Also sind auch Befehle da, um diese Komponenten zu steuern, anzulegen
und zu steuern.
Also abstrakte Maschine oder auch virtuelle Maschine gibt eine Menge
von Softwarebefehlen, die virtualisiert sind, das heißt sie werden in
Software implementiert und laufen dann auf einer echten Hardware
-Plattform oder es kann auch über Zwischenschritte gehen, zum Beispiel
die GUI-Bibliothek ist in Java implementiert, die läuft auf der
virtuellen Maschine, auf der Java VM und die wiederum ist nochmal
implementiert, deren Befehle sind nochmal durch Software
implementiert.
Also es kann durchaus eine mehrstufige Geschichte mit den abstrakten
Maschinen geben und das ist auch sehr gut, wir werden das sehen.
Das haben wir auch besprochen, ein abstrakter Maschine ist oft als
eine Programmbibliothek oder Application Program Interface, API
verwirklicht.
Betriebssystem habe ich schon erwähnt, das Betriebssystem gibt also
zum Beispiel die Prozessverwaltung, ein Prozess ist ein laufendes
Programm, Sie mal nachschauen mit Ihrem Betriebssystem, wie viele
hundert Prozesse da eigentlich unterwegs sind die ganze Zeit, wenn Sie
das mal anschauen, so an die 120, 150 Stück ist normal, aber Sie haben
möglicherweise nur einen Prozessor oder vielleicht vier.
Wir müssen also die Prozesse auf diese Prozessoren virtualisiert
werden, sodass es aussieht, als würden alle Prozesse Fortschritt
machen, weiterlaufen.
Das geht so, indem jeder ein Schrittchen eine Weile laufen darf, bis
er blockiert oder seine Zeitscheibe verbraucht ist.
Dann wird der virtuelle Speicher, schon wieder das Wort virtuell, ein
virtuellen Speicher, das heißt, Sie haben einen Speicher, der geht von
Null an adressiert, bis auf mehrere Terabyte, Sie haben natürlich
keinen Terabyte in Ihrem Rechner, aber Sie haben genügend Platz dafür,
um einen Terabyte vielleicht auf einer Platte zu verwirklichen, also
wird ein Teil dieses virtuellen Speichers auf der Platte ausgelagert
und das Transportieren der Daten von Platte zurück in den
Hauptspeicher und das im Hauptspeicher war auf die Platte zu
schreiben, dieser Austausch, das wird mit einem sogenannten
Seitenwechselmechanismus realisiert, das alles macht das
Betriebssystem.
Was hat es noch?
Datenhaltung auf Hintergrundspeicher, Kommunikation mit anderen
Rechnern ist implementiert, Kommandosprachen, grafische
Benutzoberflächen, all das liefert Ihnen diese virtuelle Maschine,
denn sonst wäre der Rechner Ihnen sehr, sehr schwer zu benutzen, wenn
Sie nur einen Assembler programmieren könnten und nichts da wäre.
Das Betriebssystem verdeckt allerdings gewisse privilegierte Befehle,
zum Beispiel die Systemaufrufe, die ich Ihnen vorhin sagte, die sind
auch sogenannte privilegierte Befehle, die führen immer dazu, dass das
Betriebssystem sagt, ja was ist denn hier los, muss ich was Besonderes
machen und es gibt natürlich auch privilegierte Befehle, um Geräte
anzusteuern, zum Beispiel zu sagen, der Platte, hol mir mal den
folgenden Sektor von deiner Oberfläche.
Wenn Sie den entsprechenden Befehl, zum Beispiel könnten Sie den im
Assemblerprogramm schreiben, das wäre durchaus möglich, Sie könnten
das übersetzen.
Das heißt übersetzen, wird ja nur übertragen in das entsprechende
Bitmuster, also diese privilegierten Befehle.
Wenn Sie die aber in einem Benutzerprogramm machen, also nicht als
Betriebssystem, privilegierter Betriebssystemschreiber ausführen,
sondern einfach nur als normales Programm, würde dieser privilegierte
Befehl entweder zum Abbruch Ihres Programmes führen, was nicht alles
heißt, es wird eine Unterbrechung gesetzt, das Betriebssystem sagt,
was ist denn hier los, oh, der Benutzer hat einen privilegierten
Befehl, das darf er nicht, daher Abbruch.
Oder aber, wie das in Microsoft ist, einfach ignoriert werden.
Darf er nicht, machen wir nicht, nächster Befehl, das ist auch eine
Möglichkeit.
Das muss natürlich zur Laufzeit überprüft werden.
Das zur Maschine, virtuelle Maschine habe ich auch schon besprochen.
Dokumentenformatiert ist auch so etwas, so etwas wie LaTeX, hat auch
nur Kommandos, mit denen können Sie formatieren.
Interpretierer macht das auch.
Ein System zur Bearbeitung elektronischer Post, E-Mail-Client, was ist
der, naja, der ist geschrieben mit einer Bibliothek.
Die Bibliothek hat Dinge drin wie E-Post schicken, empfangen,
stückeln, Anhänge mitschicken, Anhänge nachträglich holen,
Verzeichnisse speichern und so weiter, Benutzeroberfläche.
So, der letzte Punkt in dieser Geschichte der virtuellen Maschine sind
die sogenannten Makros.
Wer von Ihnen hat denn schon mal mit Makros gearbeitet, in C oder in
Assembler?
Irgendjemand schon mal Makros benutzt?
Also es ist eine Technik, die ist selten, muss man schon sagen.
Makros sind eigentlich nichts weiter als Textersetzung in Ihrem
Programm.
Zum Beispiel gibt es die Makros in C.
Hier ist so einer hingeschrieben, wir haben einen Makro, der heißt
Swap XY und er sagt, wenn immer, und hier ist die Notation, dass ein
Makro definiert wird, er sagt als folgendes, wenn immer in meinem
Programm Swap irgendwo AB steht, dann wäre das zu übersetzen mit Temp,
ergibt sich zu das, X zu Y, Y zu Temp, also die zwei Variablen zu
tauschen, wobei X und Y Parameter sind.
Es ist also nicht eine Methode, die aufgerufen wird und abläuft,
sondern dieser Text nach entsprechender Ersetzung von XY wird einfach
implantiert in Ihr Programm.
Er steht also dann nach der Makroersetzung, da gibt es eine Vorphase
in der Übersetzungsvorgang, der sagt, Makro expandieren, übrigens auch
die Include-Dateien reinholen, von denen wir vorhin sprachen, die
Header -Dateien und dann werden einfach diese Textersetzungen gemacht.
Wie gesagt, wenn Sie in C programmieren müssten, dann werden Sie das
öfters machen müssen, denn C ist ein bisschen unhandlich.
Und der Makro hat den Vorteil, dass er keinen Aufruf erfordert,
sondern einfach gewissermaßen eine Inline-Funktion, eine Inline
-Expansion einer Methode ist.
Man kann aber natürlich auch viel schlimmere Sachen anstellen, als so
etwas zu machen.
Beliebige Texte an beliebiger Stelle einflechten.
Es ist fast schon egal, was es ist, es muss keine methodenartige
Struktur sein.
Und Makros benutzt man auch sehr häufig in Assembler-Programmierung,
um nicht immer den ganzen Schwanz an Instruktionen hinschreiben zu
müssen, insbesondere beim Methodenaufruf.
Also ein Methodenaufruf mit zwei, drei Parametern kann ja locker in
die 20 Instruktionen gehen, 20 Einzel-Assembler-Instruktionen.
Da schreibt man sich ein Makro, schreiben Sie dann so etwas wie Aufruf
A und B mit dem Namen und dann macht der Makro daraus die
entsprechende Instruktionskette.
Aber hier erfolgt keine Überprüfung, dass Sie etwas nicht mehr
benutzen.
Sie können beliebige Befehle einsetzen.
Und daher kann man zum Beispiel, wenn ich jetzt die Makros, einen
Makroaufruf habe und gleichzeitig noch andere Instruktionen benutze,
die am Keller operieren, zum Beispiel den Rückgabewert setzen und
mache da einen Fehler dabei, dann bringe ich den Keller durcheinander
und damit bricht mein Programm irgendwann mal ab mit einem
Segmentation -Fault oder so.
Also Makros mit Vorsicht genießen und ich wünsche Ihnen, dass Sie
nicht viel damit zu tun haben.
Eignen Sie sich also nicht zu gut für virtuelle Maschinen.
Okay, aber es gibt noch weitere spannendere Beispiele für virtuelle
Maschinen und das sind die Cloud-Rechner.
Cloud Computing ist nichts weiter als eine virtuelle Maschine.
Und zwar ist sie sogar so, dass Sie sagen, Sie können zu einem Cloud
-Anbieter wie Amazon oder sonst wo hingehen und sagen, ich brauche
einen Rechner eines bestimmten Typs.
Ich brauche einen Linux-Rechner mit dem und dem Betriebssystem oder
ich brauche einen IBM-Rechner mit dem und dem Betriebssystem oder ich
brauche einen Macintosh-Rechner oder was immer.
Ja, machen wir, aber das heißt nicht, dass die Firmen jedes dieser
Rechnermodelle zur Verfügung hat, in ausreichender Zahl, sondern die
haben wahrscheinlich einen Rechnertyp oder vielleicht eine ganz kleine
Anzahl von Rechnertypen und wenn sie einen neuen Typ mit einem eigenen
Betriebssystem verlangen, dann wird der virtualisiert.
Und das geht so, also angenommen, Sie wollten einen IBM-Rechner
virtualisiert haben und liefern natürlich jetzt, nachdem Sie Ihre
Compiler laufen haben lassen, Programme, die auf diesem Rechner laufen
sollen und die haben natürlich den Befehlsatz des IBM-Rechners.
Aber der Cloud-Anbieter hat so einen Rechner gar nicht, den will er
gar nicht haben, weil er sagt, ich komme ganz gut zurecht mit
billigeren Rechnern als die von IBM.
Ich virtualisiere die Dinge und das heißt, dass ihr Maschinenprogramm,
das für die IBM geschrieben ist, er nimmt ein Programm, drüber laufen
lässt und dieses Programm sagt, okay, das ist Maschinenbefehl so und
so, der macht das und das, also implementiere ich den Befehl in
Software.
Er kann den interpretieren mit einem Assembler-Programminterpretierer.
Das heißt, Sie lassen eine IBM-Maschine auf eine Intel-Maschine
laufen.
Das sieht jedenfalls so aus.
Sie können das sogar so machen, dass die Virtualisierung besonders
schnell geht, dass Sie sagen, ich übersetze die Befehlsfolgen, die ich
da für den IBM-Rechner bekomme, zur Laufzeit in eine Befehlsfolge für
den Intel-Rechner, auf dem es wirklich läuft.
Ähnlich wie ein Java-Just-in-Time-Compiler.
Diese Idee haben Wissenschaftler an der Stanford-Universität
entwickelt.
Und das hat Cloud-Rechnen erst ermöglicht.
Diese Virtualisierung des Befehlssatzes anderer Rechner.
Den Befehlssatz eines Rechners auf einem anderen Rechner simulieren.
Also entweder durch Software geleistet, die die einzelnen Befehle zur
Laufzeit interpretiert und ihren Effekt nachspielt, nachsimuliert.
Oder durch dynamische Übersetzung der Befehlsfolgen der virtuellen
Maschine in Befehlsfolgen der simulierenden Maschine.
Also ein Just-in-Time-Compiler der extremen Art.
Nämlich Assembler-Code.
Laufzeit übersetzen.
Und mit dieser Virtualisierung ist es auch möglich, dass Sie sagen,
der Rechner ist jetzt unterausgelastet als Cloud-Anbieter.
Ich schiebe mal dieses Programm auf einen anderen Rechner und schalte
den aus, um Strom zu sparen.
Also Sie können auch Anwendungen umziehen lassen durch diese
Virtualisierung.
Und wieder eine abstrakte Maschine liefert einen Satz von Befehlen,
nämlich den IBM-Rechner oder was immer Sie wollen.
Und das ist in Software implementiert.
Gut, jetzt sind wir mit den abstrakten Maschinen fertig.
Gibt es noch Fragen dazu?
Also wir haben jetzt von Makro-Ebene ganz unten bis hoch zu den
virtuellen Maschinen nichts als abstrakte Maschinen gesehen.
Gibt es Fragen?
Ja, wohlgemerkt, also wenn ich einen IBM-Rechner habe, der ein Linux
-Betriebssystem hat, der für den IBM-Rechner gestrickt ist, ist er
speziell abgepasst auf den IBM-Rechner und ich lasse ihn jetzt auf
einer Intel-Kiste laufen, dann muss ich natürlich auch das
Betriebssystem übersetzen.
Laufzeit, geht alles.
Das ist eigentlich schon erstaunlich.
Nicht nur die Anwendung, ich muss ja auch das Betriebssystem aufrufen,
ich muss das ganze Dateisystem simulieren.
Alles geht.
Das ist natürlich etwas aufwendig, bis man das hat, aber sehr bequem
dann für die Cloud-Anbieter.
Ja, sehr effektiv.
So, wenn es also keine Fragen gibt, wir haben noch ein paar Minuten,
dann können wir noch ein paar Konzepte durchmachen.
Das nächste ist Programmfamilie.
Auch wieder ein Konzept von dem Herrn Parnas übrigens, der hat das
Programm Family genannt.
20 Jahre später hat man den richtigen Marketing-Term sich ausgedacht,
das heißt Software-Produkt-Linie.
Software-Produkt-Linie.
Das klingt besser, oder?
Programmfamilie ist ein bisschen mühselig.
Software-Produkt-Linie.
Die Idee ist, dass Sie mehrere Varianten eines Programms anbieten
können, die große Anteile gemeinsam haben.
Eine Menge von Programmen, die erhebliche Anteile von Anforderungen,
Intuos -Bestandteilen oder sogar Software-Komponenten gemeinsam haben.
Haben Sie alles schon gesehen?
Denn es gibt Hersteller, die liefern Ihnen eine Home-Version, eine
Professional -Version und eine Enterprise-Version.
Mit unterschiedlichem Umfang.
Das ist dann sowas wie Word oder sowas.
Dass dann in der Enterprise-Version auch noch Internet enabled ist
oder so etwas.
Und es ist natürlich klar, da steckt ein Großteil der gleichen
Software dahinter.
Da sind nur ein paar zusätzliche Funktionen immer dran geschraubt.
Und der Vorteil davon ist, dass Sie jetzt eine Produkt-Linie haben für
unterschiedliche Kundenkreise, unterschiedlich bepreist.
Je höher, desto teurer natürlich.
Und müssen aber nicht die Software komplett neu schreiben.
Das ist der offensichtliche Vorteil, nämlich die Software-Komponenten
gemeinsam haben.
Aber es kann auch sein, dass ich Intuos-Bestandteile gemeinsam habe
und sie dann doch neu implementiere.
Es kann sein, dass ich sogar nur die Anforderungen gemeinsam habe und
komplette Intuos-Implementierungen habe.
Aber ich habe nur einen Satz von Anforderungen.
Anforderungsdokumente, hatte ich Ihnen schon gesagt, sind zum Teil
sehr umfangreich, tausende von Seiten.
Wir wollen sie nicht unbedingt für jede Variante neu schreiben,
sondern es sind die gleichen Anforderungen.
Plus das, plus das, plus das und minus das, minus das, minus das.
Und dann machen wir aus der Stelle.
Ist natürlich auch keine Erfindung wirklich der Software-Techniker,
sondern gibt es an anderen Stellen natürlich auch.
Denken Sie daran, dass die ganzen Automobilgesellschaften heute mit
dieser Vielfalt von Modellen ihre Baukastensysteme haben.
Da gibt es den modularen Querbaukasten von Volkswagen.
Und auf dem wird aufgebaut der Golf und der Audi irgendwas.
Mehrere Firmen sogar teilen sich diese gemeinsamen Komponenten.
Da ist der gleiche Anlasser drin, das gleiche Intriebe, der gleiche
Motor.
Ich bin kein Maschinenbauer, aber wir sehen, dass sie auch eine
Produktlinie haben, wo sie Bauteile mehrfach verwenden.
Die gleiche Schadsoftware, die gleiche Abstellsoftware für die
Abgasreinigung.
Es ist überall das Gleiche drin.
Aber Wiederverwendung ist natürlich der Punkt.
Und damit kostengünstiger zu werden.
Das ist eine Produktlinie, das alles von vorne zu entwickeln.
Also wir möchten Gemeinsamkeiten nutzen, Wiederverwendung von
Entwürfen, Spezifikationen, Anforderungen, Softwarekomponenten,
Bibliotheken erreichen, um Reduktion in der Entwicklung und
Wartungskosten zu erreichen.
Bei den Wartungskosten natürlich klar, wenn ich einen Fehler finde und
ich muss das nur an einer Stelle flecken, dann ist es viel
geschickter, als es an 100 Stellen reparieren zu lassen.
In der Programmfamilie oder Softwareproduktlinie spricht man auch von
sogenannten Variationspunkten.
Das heißt, das sind Punkte, wo geplant ist, dass man Änderungen oder
Weiterungen einfügt, an denen Alternativen zum Beispiel bezüglich
Plattformen, bezüglich Benutzerschnittstelle eingeführt werden können.
Sie wollen vielleicht eine Anwendung schreiben, die läuft sowohl auf
einem PC, als auch auf einem Mac, als auch auf einem mobilen Gerät.
Und auf dem mobilen Gerät gibt es auch zwei, drei Varianten.
Und da gibt es immer Variationen, gerade in der Benutzeroberfläche.
Und da liegt es nahe zu sagen, die Benutzeroberfläche ist so ein
Variationspunkt.
Aber es gibt eine unterschiedlich unterlegene virtuelle Maschine.
Das ist der Anwendungskern, der ist in allen Anwendungen der gleiche.
Hoffentlich.
Okay, Programmfamilie.
Wie unterscheiden sie sich?
Wie unterscheiden sich die Mitglieder einer Produktlinie?
Naja, sie unterscheiden sich, wo sie auf verschiedenen Hardware- oder
Betriebssystemen -Konfigurationen laufen.
Sie entscheiden sich vielleicht in Format der eigenen Ausgabe.
Jemand will ein besonderes Format haben, XML oder etwas anderes, ein
internes, keine Ahnung.
Sie stellen sich im Funktionsumfang.
Sie denken an Home Professional Enterprise Versionen, da manche
Benutzer nur eine Teilmenge der Funktionalität brauchen und für den
größeren Teil nicht bezahlen wollen.
Auch die Betriebsmittel, Hauptspeichern dafür nicht aufbauen wollen.
Wollen aber auch keine Effizienzeinbußen hinnehmen.
Wenn sie nur einen beschränkten Funktionsumfang wollen, dann können
wir das liefern.
Intern können sie sich natürlich auch unterscheiden.
Sie können sich in Datenstrukturen und Algorithmen unterscheiden.
Wenn sie wissen, dass sie auf einem Server-Implementierung eines
Mitglieds der Produktfamilie eben sehr viel Speicher haben, dann
wählen sie unter Umständen teurere Datenstrukturen, die dann auch
schneller laufen.
Während wenn sie auf einem PC oder möglicherweise sogar ein
Mobiltelefon sind, da haben sie nur ganz eingeschränkt Speicher, dann
sagen sie, da muss ich Speicherplatz sparen oder dafür möglicherweise
mehr rechnen, damit es überhaupt auf dem Mobilgerät passt.
Das ist so eine Unterscheidung, die von den Plattformen kommt.
Es kann auch sein, dass Eingabedaten oder relative Häufigkeiten
gewisser Ereignisse sich unterscheiden.
Bestimmte Anfragen in bestimmten Umgebungen sehr häufig kommen und in
anderen ganz selten sind.
Dann können sie dafür unterschiedliche Implementierungen wählen, die
bei dem einen eben hochperformant sind und beim anderen weniger.
Dafür auch weniger Speicherplatz erfordern.
Genau, so.
Jetzt noch weiterschauen.
Das ist eine Nebenbewertung, eine Unterscheidung zwischen allgemein
und flexibel.
Allgemein heißt, die Software kann alles.
Sie können alles einstellen, in vielen Assoziationen ohne Änderungen
benutzt werden kann.
Oder ein Programm ist flexibel, das heißt sie können es abändern und
dann neu liefern um in einer neuen Situation gebraucht zu werden.
Also ich könnte mir eine sehr allgemeine Armbandur vorstellen.
So eine, die ich auch so umstellen kann, dass sie mit der 10-Tage
-Woche arbeitet.
Hat man ja im dritten Reich mal gehabt, hat nicht funktioniert.
Vielleicht, wenn man sagen würde, das könnte mal kommen, dann mache
ich es ohne Hersteller.
Ich bin vorbereitet auf die 10-Tage-Woche, ein Knopfdruck und dann
läuft es für 10 Tage.
Ich weiß nicht, wie die extra drei Tage heißen, aber ist auch egal.
Das wäre sehr allgemein.
Wer will das?
Niemand will das.
Also bauen Sie lieber Software, die Sie sagen können, wenn es sein
muss, für den seltenen Fall, dass wir auf die 10-Tage-Woche gehen
müssen, für ein bestimmtes Land vielleicht, dann können wir die
Software da an der Stelle ändern, indem wir die Zahl N von 7 auf 10
setzen.
Dann läuft alles.
Oder zusätzliche Algorithmen einbauen müssen.
So, aber wir konzentrieren uns hier hauptsächlich auf den
änderungsfreundlichen Entwurf.
Das heißt also, den Entwurf so zu gestalten, dass voraussehbare
Änderungen, andere Plattformen, Mobil, Nicht-Mobil, Internet oder
Nicht -Internet, dass solche Dinge durch geringfügige Änderungen,
möglicherweise durch das Setzen von ein, zwei Modulen erledigt werden
kann.
So, das waren jetzt Produktfamilie und virtuelle Maschine.
Produktlinie und virtuelle Maschine.
Und jetzt kommen die Architekturstile.
Da haben wir noch vier Minuten.
Da machen wir kurz die Schichtenarchitektur, weil ich die schon
mehrmals erwähnt habe von heute und den klärenden Dienstgeber dann
nächstes Mal.
Schichtenarchitektur ist eine Gliederung von hierarchisch geordnete
Schichten.
Das zeige ich Ihnen am besten als Komponent, als diesem Beispiel.
Hier hätten wir drei Schichten.
Was ist in dieser Schichtenarchitektur erlaubt und was ist nicht
erlaubt?
Erlaubt ist, dass Komponenten in dieser Schicht, es sind in der Regel
mehrere Komponenten, in dieser Schicht nach unten aufrufen dürfen,
also die unterliegende Schicht aufrufen.
Und diese Schicht kann noch diese untere Schicht aufrufen.
Aber es gibt keinen Aufruf, der nach oben geht.
Daher ist sie auch zyklenfrei.
Das wissen wir dann durch Konstruktion dieser Schichtenarchitektur.
Innerhalb einer Schicht ist alles erlaubt.
Die Komponenten können sich gegenseitig aufrufen, wie es benötigt ist.
Aber sonst nur Aufruf nach unten.
Und es gibt noch den Fall, dass sich auch eine Schicht überspringen
darf.
Das hier, wo man, aber das ist eine Diskussionsfrage, ob das erlaubt
ist oder nicht.
Wenn es erlaubt ist, Schichten zu überspringen, dann ist das
sogenannte transparente Architektur, weil gewissermaßen diese Schicht
hier transparent, also durchsichtig wird.
Andernfalls nennt man es die intransparente Architektur.
Hängt davon ab, was man machen will.
Insbesondere wenn solche Aufrufe diese Schicht hier durcheinander
bringen können, dann würde man das nicht erlauben.
Also, eine Schicht heißt auf Englisch Layer oder Tier, ist ein
Subsystem, welches Dienste für andere Schichten zur Verfügung stellt.
Wird übrigens manchmal auch als virtuelle Maschine bezeichnet.
So eine Schicht als eine virtuelle Maschine.
Die Aufrufe gehen alle nach unten.
Und die Subsysteme in einer Schicht können beliebig aufgeteilt werden.
Intransparente Schicht ist so, dass Schicht A Schicht B aufrufen kann,
Schicht B kann Schicht C aufrufen.
Ein Überspringen einer Schicht ist nicht erlaubt.
Und ein Rückruf nach oben ist auch nicht erlaubt.
Weder der, noch der, noch ein Übergreifender.
Das ist die sogenannte intransparente Schichtenarchitektur.
Opaque auf Englisch oder opaque, wenn wir so wollen.
Bei einer transparenten Schichtenarchitektur werden durchlässige
Schichten erlaubt.
Ich kann also nach unten aufrufen, direkt nach unten, aber auch eine
Schicht überspringen.
Aber das nach oben rufen ist mir weiterhin verboten.
Beides ist möglich.
Wir haben damit eine übersichtliche Strukturierung, ist klar, wo alles
hingehört.
Und Sie haben natürlich, selbstverständlich arbeiten Sie mit
Schichtenarchitekturen die ganze Zeit.
Sie haben eine schreibende Anwendung, die wird übersetzt auf die
virtuelle Maschine.
Ist schon wieder eine Schicht, die virtuelle Maschine wird
implementiert.
Die nutzt außerdem noch das Betriebssystem.
Ist auch noch eine virtuelle Maschine, die mitbenutzt wird.
Also sind wir eigentlich damit vertraut.
Wir haben vielleicht nur nicht darüber nachgedacht.
Keine Einschränkungen innerhalb einer Schicht.
Und es wird jetzt Wiederverwendbarkeit vereinfacht.
Ich kann also zum Beispiel sagen, wenn ich mir diese
Schichtarchitektur nochmal vornehme.
Bin gleich fertig.
Diese Schicht hier, sagen wir, das wäre zum Beispiel die Datenhaltung.
Und ich brauche jetzt ein neues Datenbanksystem von einem anderen
Hersteller.
Ich gehe von SAP zu Oracle, von Oracle zu IBM.
Solange die die gleiche Schnittstelle liefern, tausche ich einfach
diese Schicht auf und laufe dann unter Umständen auf einem neuen
Betriebssystem.
Wohin ich konnte.
Ich kann also die untere Schicht austauschen.
Ich kann auch obere Schichten austauschen.
Zum Beispiel eine andere Benutzeroberfläche dran schrauben und den
Rest wieder gleich behalten.
Ist also in dem Sinn durchaus änderungsfreundlich, weil ich einzelne
Schichten ersetzen kann.
Internen Schichten was zu ersetzen, ist etwas komplizierter.
Das erfordert etwas mehr Feinarbeit.
So, was habe ich da noch zu sagen?
Jetzt kann man nochmal vorwärts.
Intransparente Schichten kann es zu Effizienzverlusten führen.
Das hat damit zu tun, das zeige ich Ihnen am Bild.
Angenommen, Sie dürfen über diese Schicht B nicht hinübergreifen.
Also Schicht C von Schicht A nicht direkt ausgreifen.
Also muss B eine äquivalente Funktion bereitstellen.
Die sieht dann im Wesentlichen so aus, dass sie delegiert.
Sie haben also eine Funktion X hier unten und dann haben Sie hier die
Funktion X nochmal und Ihre Implementierung leitet nur weiter an die
nächste Schicht.
Und wenn Sie also eine Schichteregulierung haben mit 25 Schichten oder
sowas, dann kann das schon diese Delegierung etwas Zeit erfordern.
Also immer runterrufen, runterrufen, nochmal, nochmal, nochmal,
nochmal und schließlich doch erst rechnen.
Das kann ein Effizienzverlust sein, ein möglicher Nachteil.
Man unterscheidet übrigens eine Dreischichten- und eine dreistufige
Architektur.
Bei der Dreischichtenarchitektur sagt man einfach, dass drei
hierarchisch geordneten Subsystemen bestehen.
Benutzer, Schnittstelle, Anwendungskern, Datenbanksystem zum Beispiel.
Eine dreistufige oder im Englischen eine Three-Tier-Architecture, das
ist eine, bei welcher die Schichten auf unterschiedlichen Rechnern
laufen können.
Zum Beispiel könnte also die Benutzerschnitt auf dem Klientenlaufen
ein Teil der Anwendungskern und der Datenbank auf einem Dienstgeber
übers Netz verteilt.
Dann spricht man von einer Three-Tier-Architecture.
Wir sind nicht beschränkt auf eine Anwendung, auf drei Schichten.
Das hier ist zwei Anwendungen, vier Schichten zum Beispiel.
Und hier gibt es noch ein Beispiel für eine Vierschichtenarchitektur
und so weiter.
Also hier gibt es noch Beispiele.
Da machen wir dann nächstes Mal weiter.
Danke.