Dann würde ich jetzt tatsächlich anfangen mit der Entwurfsphase.
Wie gesagt, wir haben jetzt schon zwei Phasen im Wasserfall
abgearbeitet.
Wir sind schon durch die Planung durch, wir sind durch die Definition
durch und wir haben jetzt ein schönes Pflichtenheft, in dem sehr gut,
hoffentlich drin steht, was wir denn tun wollen mit unserer Software.
Damit haben Sie sich jetzt schon ausführlich beschäftigt, haben schöne
Anforderungsdokumente zur Hand unterschiedlicher Art, haben Use-Case
-Diagramme und so weiter und so fort.
Und jetzt geht es eben darum, die Software zu entwerfen.
Das heißt, jetzt geht es darum, zu klären, wie das Ganze funktionieren
soll.
Bisher haben wir nur gesagt, was wir tun wollen, aber das Wie war
bisher noch völlig ungeklärt.
Und dazu schauen wir uns in der Vorlesung fünf Teilkapitel an.
Wir machen eine kleine Einführung, dann machen wir weiter mit dem
modularen Entwurf.
Das ist so ein bisschen 1970, denken Sie jetzt vielleicht, weil wo
gibt es denn schon noch Module?
Wir arbeiten doch hier objektorientiert.
Ja, das ist richtig, aber A, gab es den modularen Entwurf zuerst.
Insofern ist es didaktisch auch sinnvoll, damit anzufangen.
Und zweitens gilt alles, was im objektorientierten Entwurf gelten
soll, auch schon im modularen Entwurf.
Das heißt, wenn Sie den modularen Entwurf gut verstanden haben, dann
kommt im Grunde im objektorientierten Entwurf nur noch so ein bisschen
was on top.
Deshalb machen wir das so rum und halten auch dann Kapitel 3.3
objektorientierten Entwurf relativ kurz, weil wie gesagt, so furchtbar
viel kommt nicht dazu.
Aber mit dem objektorientierten Entwurf kann man halt andere schöne
Dinge tun, die man vorher nicht konnte und kann schöne
Architekturmuster verwenden oder auch Entwurfsmuster.
Und das sind so die zwei Kapitel, von denen Sie vielleicht in ein,
zwei Jahren mal sagen werden, oh, die haben ja aber richtig was
gebracht in der SVT.
Normalerweise wären das während der Vorlesung sehr unbeliebt, weil man
muss sich da reindenken, muss versuchen zu verstehen, warum das
wichtig ist.
Aber ich kann Ihnen sagen, es ist extrem wichtig.
Und spätestens wenn Sie im PSE sind, werden Sie verstehen, warum man
Architekturmuster und Entwurfsmuster braucht.
Aber ich glaube, dazu werden wir auch einfach noch genaueres sagen,
wenn wir tatsächlich in den Kapiteln sind.
Heute machen wir wahrscheinlich nur 3.1 und 3.2, je nachdem wie es
läuft und würden gleich mal mit einem Überblick anfangen.
Was ist jetzt eigentlich die Aufgabe im Entwerfen?
Wie gesagt, wir haben unsere Anforderungsdokumente.
Und jetzt geht es eben darum, eine Lösung für die Anforderungen zu
finden.
Das heißt, wie baue ich jetzt meine Software?
Wie entwerfe ich eine schöne Software-Architektur, um das Problem zu
lösen?
Hier steht nochmal das, was ich gerade eben schon gesagt habe.
Es geht um das Wie.
Wir haben bisher nur das Was geklärt.
Jetzt geht es darum, wie bauen wir denn unsere Software?
Und das Ganze nennt man auch Programmieren im Großen.
Warum, werden wir gleich auch nochmal sehen.
Was ist jetzt so eine Software-Architektur?
Das war so ein Stichwort auf der letzten Folie.
Na ja, eine Software-Architektur gliedert Software-Systeme in
Komponenten.
Das können Module sein, das können aber dann später auch Klassen sein.
Und wir gliedern weiter dann auch noch in Subsysteme.
Je nachdem, wo sie sich bewegen, Pakete, Bibliotheken, wie auch immer.
Und was dabei entsteht, ist so eine Hierarchie.
Was wir außerdem noch tun müssen in unserer Software-Architektur, die
einzelnen Komponenten natürlich genau spezifizieren und eine
sogenannte Benutzt-Relation aufstellen.
Das heißt also, Sie bauen beim Entwurf diese einzelnen Komponenten
auf.
Module, Klassen, Subsysteme, Pakete.
Und dann ziehen Sie eben diese Uses-Kanten, die Sie schon im UML
-Klassendiagramm kennengelernt haben und zeigen damit an, wer hier
eigentlich was benutzt.
Dann gibt es noch weiterhin optional den Feinentwurf.
Je nachdem, wie weit man da gehen möchte, kann man tatsächlich
Datenstrukturen und Algorithmen meist im Pseudocode angeben schon.
Und was man natürlich auch tun muss, wenn man zum Beispiel verteilte
Systeme baut, dann muss man auch mal angeben können, welcher Teil,
welches Subsystem jetzt auf welchem Rechner läuft, auf welchem Knoten
in Ihrem verteilten System.
Gut, hier nochmal in kurz und knapp.
Eingabe, unsere Anforderungsdokumente,
Pflichtenheft, Benutzeroberflächenkonzept, Benutzerhandbuch,
Hilfekonzept, alles das, was wir bisher uns erarbeitet haben.
Und dann kommt eben dieser Entwurfsprozess.
Warum steht da Prozess und nicht einfach nur Entwurf?
Weil Sie feststellen werden, dass es halt normalerweise eben nicht so
geht, dass Sie einfach sich die Dinge anschauen, die Sie bekommen und
dann sagen, ich habe jetzt eine furchtbar gute Idee, wie ich das jetzt
sofort einfach mal runterentwerfe.
Und dann zeichnen Sie das einmal auf und so ist dann Ihr System
fertig.
Sondern Sie brauchen da wahrscheinlich mal ein oder zwei oder drei
Anläufe für, müssen Dinge verfeinern, stellen fest.
Hier ist es nicht so ganz optimal entworfen.
Ich mache das jetzt nochmal neu.
Und genau dieser Prozess ist wichtig, weil Ihre Software, die entsteht
hier an dieser Stelle.
Die Software entsteht nicht, wenn Sie implementieren.
Das ist der entscheidende Schritt.
Hier entscheiden Sie, wie Ihre Software später funktioniert.
Wenn Sie einen schlechten Entwurf bauen, können Sie nachher so gut
implementieren, wie Sie wollen.
Dann ist Ihre Software trotzdem kaputt.
Weil der Entwurf halt schon völlig daneben liegt.
Und dementsprechend ist es extrem wichtig, sich bewusst zu sein, dass
das der entscheidende Schritt ist im Entwicklungsprozess der Software
und dass das Ganze eben ein Prozess ist.
Dass Sie normalerweise eben nicht einfach nur einen ersten Entwurf
bauen und der ist es dann.
Gut.
Welche Fragen müssen auf dem Weg geklärt werden?
Naja, das fängt an mit solchen Sachen wie gibt es vielleicht schon
Bibliotheken, die wir einfach verwenden können?
Dann können wir uns einfach Teile sparen, müssen Dinge gar nicht
entwerfen, die vielleicht gefordert waren durch unsere Anforderungen.
Hat aber häufig zur Folge, dass Sie Ihren restlichen Entwurf anpassen
müssen.
Sie können eben, wenn Sie Drittanbieter-Software verwenden, nur die
Schnittstellen verwenden, die dort gegeben sind.
Das heißt also, Sie müssen Ihre Software, die vielleicht andere Dinge
wollte, ein bisschen anpassen.
Also auf solche Dinge müssen Sie achten.
Dann müssen Sie sich mit solchen Dingen beschäftigen wie
Echtzeitbedingungen.
Wir hatten ja als nicht funktionale Anforderungsmöglichkeit eben
Echtzeit als Anforderung.
Und da müssen Sie sich jetzt Gedanken machen, wie kann man denn sowas
sicherstellen?
Oder wie lege ich denn Daten dauerhaft ab?
Lege ich die in Dateien ab oder benutze ich Datenbanken?
Beides hat Vor- und Nachteile.
Wenn Sie selber Dateien bauen und die irgendwo ablegen, dann haben Sie
eben die volle Macht über Ihre Dateien.
Aber Sie müssen sich auch um alles kümmern.
Wenn Sie eine Datenbank benutzen, dann wird Ihnen das im Grunde durch
die Datenbank-Software abgenommen.
Aber Sie müssen halt sehen, dass Sie die sinnvoll ansprechen.
Und dann natürlich solche Sachen wie die Dynamik.
Das heißt also, was passiert denn überhaupt im Ablauf?
Haben wir ein monolithisches System, wo einfach alles aufrufgesteuert
ist?
Läuft einfach von vorne nach hinten durch?
Oder haben wir vielleicht ein eventbasiertes, ereignisgetriebenes
System?
Oder gibt es vielleicht parallele Ausführungen?
All das muss jetzt berücksichtigt werden, wenn Sie denn Ihr System
strukturieren.
Und dann natürlich Fragen, die hinten dran kommen.
Wie wird das System installiert, gestartet, angehalten?
Was wird bei Fehlern, bei Ausnahmen gemacht?
Wie kann man das mit den Zugriffsrechten durchsetzen?
In Ihrem Anforderungsdokument stand bestimmt sowas drin, wie
Administratoren dürfen XY verwalten.
Das ist wieder so ein typisches Beispiel für, was soll passieren.
Das ist aber noch lange nicht geklärt, wie man sowas durchsetzt.
All sowas müssen Sie jetzt beachten, wenn Sie entwerfen.
So, was haben wir hier?
Noch mal eine kurze Übersicht darüber, wem welche nicht funktionalen
Anforderungen wichtig sind.
Ist offensichtlich, dass Entwickler auf andere Dinge Wert legt.
Als der Auftraggeber oder Endbenutzer.
Häufig sind auch die letzten beiden einfach das Gleiche.
Aber kann auch eben diese Dreiteilung sein.
Was wichtig ist für Sie an der Stelle, egal auf welcher Seite Sie
sind, sind Sie jetzt Entwickler, Auftraggeber oder Endbenutzer.
Die sehen, wie Sie die Software gerne hätten, von den nicht
funktionalen Anforderungen her.
Aber versuchen Sie auch immer an die anderen Seiten zu denken.
Also wenn Sie Entwickler sind, denken Sie sich, okay, ich hätte halt
gerne saubere Schnittstellen, weil damit kann ich gut arbeiten.
Und dokumentiert soll das auch sein.
Da hat bestimmt auch der Endbenutzer was davon.
Aber Sie werden sicherlich nicht darum herumkommen, auch mal an die
niedrigen Kosten zu denken.
Oder auch an Rückwärtskompatibilität, weil die ist vielleicht dem
Auftraggeber wichtig.
Das heißt, Sie müssen da so ein bisschen auch aus Ihrem Schneckenhaus
rauskommen und mal darüber nachdenken, was vielleicht auch von anderer
Seite wichtig sein kann, wenn Sie Ihre Software entwickeln.
Gut, was sind denn so typische Entwurfsabwägungen, gerade bei den
nicht funktionalen Anforderungen?
Na ja, Sie haben ja gerade gesehen, es gibt hier einfach eine riesige
Menge nicht funktionale Anforderungen.
Und wie das so häufig ist, man kann einfach nicht alles gleichzeitig
erfüllen.
Und das ist hier ganz besonders der Fall.
Das heißt, man muss immer so ein bisschen abwägen, wie kann man denn
jetzt einen guten Trade-off machen, zum Beispiel zwischen Laufzeit und
Platzbedarf.
Also, baue ich meine Software entweder so, dass sie alles im Speicher
vorhält, dann brauche ich extrem viel Speicher, aber bin super schnell
im Zugriff.
Oder berechne ich die Dinge, die ich brauche, jedes Mal.
Dann ist die Laufzeit schlechter, aber ich brauche quasi keinen
Speicher.
Und davon gibt es einige Abwägungen, die man treffen kann.
Wirklich spannend ist eigentlich die letzte.
An die sollten Sie eigentlich immer denken.
Good, fast, cheap, pick any two.
Das ist so eine Regel, die ist eigentlich allgemeingültig.
Sie können Ihre Software gut und schnell entwickeln, aber dann wird es
nicht billig, weil dann müssen Sie dem Auftraggeber sagen, wir können
dir eine super Software in fünf Wochen hinstellen, aber da brauchen
wir noch 20 Entwickler dazu.
Dann wird es halt teuer.
Und dann sagt er, ja, nee, also die zwei Entwickler, die ich jetzt
bezahlt habe, die müssen reichen, aber es soll genauso schnell werden.
Was ist das Ergebnis?
Es wird nicht mehr so gut.
Und das ist eine Regel, die eigentlich immer gilt.
Seien Sie sich dessen bewusst.
Gut, wenn wir all das geklärt haben, uns über funktionale Dinge
Gedanken gemacht haben, über nicht funktionale Dinge Gedanken gemacht
haben, dann können wir eben den ersten Entwurf wagen.
Und ich habe schon darauf hingewiesen, im Wesentlichen gibt es eben
diese beiden.
Es gibt den modularen Entwurf, mit dem wir uns jetzt zuerst
beschäftigen.
Und dann gibt es den objektorientierten Entwurf, der dann im Anschluss
kommt.
Und was Sie sich hier an dieser Stelle nochmal merken sollten, ist,
hören Sie zu bei dem, was ich Ihnen zum modularen Entwurf erkläre,
weil alles, was da gilt, gilt auch für den objektorientierten Entwurf.
Es kommen im Grunde nur noch Dinge hinzu.
Vererbung, Polymorphie, Datenmodellierung, Instanzen, das kommt hinzu,
das haben Sie im modularen Entwurf nicht.
Nichtsdestotrotz gilt der ganze Rest des modularen Entwurfs auch.
Deshalb werden wir uns hauptsächlich mit dem beschäftigen.
Und genau damit fangen wir jetzt auch gleich an.
Wenn Sie das Ganze nochmal nachlesen wollen, wenn Ihnen das hier alles
zu schnell geht, empfehle ich Ihnen den Balzert Band 1, wenn Sie einen
zu Hause haben.
Ansonsten müssen Sie halt in die Bibliothek gehen.
Ich glaube, als E-Book gibt es den nicht.
Schauen Sie da mal rein, da ist das auch alles sehr gut aufgeführt.
Gut, wie sieht jetzt die Systemarchitektur beim modularen Entwurf aus?
Naja, wir haben zum einen den Modulführer, in dem im Grunde genau das
aufgeführt wird, was ich vorhin schon gesagt habe.
Wir wollen im Grunde sagen, wie sind denn die einzelnen Komponenten,
die einzelnen Module unserer Software organisiert?
Gibt es Subsysteme?
Was macht denn das einzelne Modul?
Das muss eben beschrieben werden.
Und was wir dort auch schon machen können, ist natürlich
Entwurfsmuster benutzen, die wir uns dann im Kapitel 3.5 anschauen
werden.
Zum Beispiel steht hier Schichtenarchitektur oder
Fließbandarchitektur.
Tatsächlich sind das eher Architekturstile, aber da ist der Übergang
auch fließend.
Sollte man vielleicht mal überdenken, ob man da nicht andere Beispiele
auf den Folien nimmt.
Denn eigentlich gibt es einen Unterschied zwischen Architekturstilen
wie eben Schichtenarchitekturen, Fließbandarchitekturen und dann den
Entwurfsmustern.
Aber es ist auch eigentlich nur ein Granularitätsunterschied, sage ich
mal.
Was steht außerdem noch in der Systemarchitektur im modularen Entwurf,
sind die Modulschnittstellen.
Da beschreiben sie im Grunde die Außensicht.
Also was stellt das Modul, was sie dort bauen, nach außen hin zur
Verfügung.
Und sie geben natürlich an, was ist Ein- und Ausgabe.
Normalerweise passiert das informell, teilweise aber auch formell.
Das ist aber normalerweise nur dann der Fall, wenn ihr System
besonders sicherheitskritisch ist oder aber sie von ihrem Auftraggeber
zu viel Geld bekommen haben und der sagt, dafür möchte ich jetzt aber
auch eine super Doku haben.
Normalerweise, wie gesagt, eher informell an der Stelle.
Was müssen sie außerdem noch tun?
Eine Benutztrelation aufstellen.
Das heißt also, wenn sie ihr System gegliedert haben, müssen sie eben
angeben, was braucht hier was.
Welches Modul ist von welchem anderen Modul abhängig und was sie dort
eigentlich erreichen wollen, ist, dass ihre Module und Subsysteme und
was auch immer sie da gebaut haben, mit dieser Benutztrelation einen a
-zyklischen gerichteten Graph erzeugen.
Kann sich jemand vorstellen, warum das wichtig sein könnte, dass man
das so baut?
Da meldet sich jemand, bitte.
Ihr Kommilitone sagt, vielleicht merkt man, dass unterschiedliche
Module so eng miteinander verwoben sind, dass man die vielleicht eher
in ein Modul packen sollte.
Das kann eine mögliche Folgerung sein, ist aber eher selten.
Wir möchten eigentlich nicht Dinge, die sich vorher getrennt haben,
dann wieder zusammenfügen.
Das kann passieren, das möchte ich nicht ausschließen, aber es ist
eigentlich nicht das, was man machen möchte, weil gerade im Zweifel
haben wir gesagt, bauen Sie lieber eine Klasse mehr als eine zu wenig.
Dementsprechend sollten Sie das vielleicht eher nicht tun.
Es gibt noch weitere Wortmeldungen.
Da kommt schon ein erster sehr guter Hinweis.
Und zwar geht es ein bisschen darum, in welcher Reihenfolge ich dann
später mein System baue.
Wenn unterschiedliche Module voneinander abhängen, dann sollte ich
vielleicht erst mal das bauen, von dem mehr abhängt, weil dann kann
ich später auch Dinge testen.
Wenn ich das nicht habe, dann muss ich alles fertig bauen, bevor ich
testen kann, wenn ich mir darüber keine Gedanken gemacht habe, weil
ich ja nicht weiß, braucht jetzt vielleicht ein Modul XY, Modul Z.
Wenn ich das nie geklärt habe, habe ich eben genau das Problem.
Ich glaube, Tobias sagt nachher auch noch genauer was zu diesem
Beispiel.
Noch etwas anderes.
Ansonsten erzähle ich Ihnen noch eine kleine Anekdote an der Stelle.
Und zwar habe ich vor kurzem einen Vortrag gehört von einer Kollegin,
die hat eben genau über dieses Problem promoviert vor gut zehn Jahren.
Und deren Promotionsthema war, kann man anhand dieser benutzten
Relation Softwarequalität ablesen?
Und das Ergebnis war, ja, das kann man ziemlich gut.
Und was soll ich sagen, inzwischen hat sie aus dieser Idee eine Firma
gemacht und verdient unglaublich viel Geld, weil sie in Firmen geht
und deren Software aufräumt.
Stellen Sie sich das ungefähr so vor wie diese Sendung auf einem
Privatkanal, die Bauretter.
So ungefähr funktioniert das.
Wenn Leute unwartbare Software haben, dann wird eben diese Firma
gerufen und die lassen dann ihr Werkzeug laufen, was im Wesentlichen
eben genau diese benutzten Relationen aufbaut und dann eben schaut,
wie die Software strukturiert ist und dann eben sagt, also das ist ein
hoffnungsloser Fall, wegwerfen, neu bauen.
Oder die sagt, da oben das Modul, das ist ganz schlecht, davon hängt
alles ab, das sollten Sie nicht machen.
Und so ganz typische Sachen sind eben einmal diese nicht gerichteten
Graphen, das ist immer blöd, wenn halt Abhängigkeiten in alle
möglichen Richtungen gehen, dann kann ich eben nicht mehr Teile von
meinem System bauen und unabhängig voneinander testen, wie ihr
Kommilitone gesagt hat.
Was auch noch so ein Klassiker ist, sind diese Gottklassen, von denen
Martin letztes Mal schon berichtet hat.
Also wenn Sie so eine benutzte Relation aufbauen und dann führt Ihr
Diagramm einen Stern, dann haben Sie wahrscheinlich irgendwas falsch
gemacht.
Und ja, noch sowas anderes, was man vielleicht auch häufiger sieht,
wenn man sich mit Bibliotheken beschäftigt oder wenn man selber schon
mal eine etwas größere Software gebaut hat, dann kommt man häufiger
mal in die Gelegenheit, dass man in die Verlegenheit, dass man nicht
weiterkommt und dann baut man sich halt so eine Utility-Klasse oder so
eine Helper-Klasse.
Und bisher hat man alles wunderbar gebaut, alles schön gerichtet und
schön hierarchisch und hängt alles nur in eine Richtung voneinander
ab.
Und jetzt baut man so einen Helper und der greift halt überall rein.
Macht man ganz häufig, sieht man ganz häufig in Bibliotheken, müssen
immer darauf achten.
Das macht halt so eine ganz schöne vorher aufgebaute Hierarchie
kaputt.
Okay, ich hoffe, dass es an der Stelle klar geworden, warum das
wichtig ist.
Denken Sie darüber nach.
Lassen Sie nicht einfach alles von allem abhängen und wenn, dann in
einer sinnvollen Art und Weise.
Was steht außerdem noch in unserer Systemarchitektur jetzt mit drin?
Der Feinentwurf.
Optional, natürlich nur.
Je nachdem, ob das denn nötig ist.
Das macht man häufig dann, wenn man weiß, am Ende muss man ein
Assembler programmieren.
Dann schreibt man halt das, was man sich vorstellt, was da an
Algorithmik passieren soll, eben bei einer Hochsprache beziehungsweise
im Pseudocode.
In einem Pseudocode, der einer Hochsprache ähnelt, sage ich jetzt mal,
einfach hin.
Und dann kann dann der arme Assembler-Programmierer sehen, wie das ein
Assembler umsetzt.
Deshalb macht man das eigentlich.
Sie können aber natürlich auch einfach Ihrem späteren Entwickler
Datenstrukturen vorgeben.
Oder wenn Sie wissen, hier muss was sortiert werden und sagen, ja, ich
weiß, hierfür das System wäre Sortieralgorithmus XY.
Gut, dann schreiben Sie das einfach mit dazu.
Gut, wir unterscheiden beim modularen Entwurf zwischen dem externen
Entwurf.
Das ist im Grunde die Außensicht.
Da haben Sie nur diesen Grob-Entwurf und haben im Grunde die
Draufsicht auf Ihr System.
Dort sehen Sie die Modul-Schnittstellen.
Sehen im Grunde, wie kann man später Ihre Software von außen benutzen.
Und auf der anderen Seite haben wir den internen Entwurf.
Und dort sieht man eben das ganze Innenleben.
Da sehen Sie die benutzten Relationen, wissen was wovon abhängt.
Und vielleicht auch noch den feinen Entwurf.
Je nachdem, ob man den braucht oder nicht.
Hier steht jetzt noch zum x-ten Mal der Hinweis, alles was hier gültig
ist, ist auch im objektorientierten Entwurf gültig.
Ich glaube, das sollte jetzt auch beim letzten angekommen sein.
Deshalb gehe ich da jetzt nicht noch mal weiter darauf ein.
Wenn wir uns jetzt mit Modulen genauer beschäftigen, dann ist pro
Modul wichtig, dass man das eben einzeln benutzen kann und unabhängig
von anderen Modulen bearbeiten kann.
Das heißt auch, und jetzt wird es ein bisschen knifflig, eigentlich
sollte ein Modul ohne Kenntnis der späteren Nutzung implementiert,
getestet und überarbeitet werden können.
Also denken Sie mal an die Übungsaufgabe.
Da sollten Sie ein Dreieck bauen.
Das können Sie bauen, ohne zu wissen, dass Sie das später mal in diese
Kunstfilter -App einbauen müssen.
Weil ein Dreieck, das ist einfach allgemein gültig.
Dessen Eigenschaften und Funktionen kann man einfach implementieren,
ohne zu wissen, was später damit gemacht wird.
Und genau das sollte eben gehen, unabhängig von allem anderen, was in
Ihrer Software passiert.
Und das hat eben zur Folge, dass die Implementierung des Moduls auch
möglich wird, ohne die Implementierungsdetails der anderen Module
kennen zu müssen.
Und das heißt dann wiederum im Endeffekt, wenn Sie das ordentlich und
sinnvoll bauen, kann man Ihr Modul benutzen, ohne dass man weiß, wie
das denn im Inneren aufgebaut ist.
Das heißt, wenn Sie sinnvolle Schnittstellen definiert haben für Ihr
Modul, dann muss man gar nicht wissen, haben Sie Sortieralgorithmus XY
benutzt?
Wie stellen Sie Punkte in Ihrem Dreieck dar?
Wie stellen Sie die Kanten dar?
Ist völlig egal, weil es gibt dann einfach sinnvolle Schnittstellen
dazu und darüber kann man das benutzen.
Gut, was ist dann da so der Normalfall?
Der Normalfall ist, dass man eben in einem Modul mehrere
Unterprogramme hat, die wiederum eine Datenstruktur manipulieren.
Und was wir jetzt eigentlich haben wollen, ist, dass andere Module in
diese Datenstruktur eben nicht reingreifen wollen.
Da schauen wir gleich nochmal ein bisschen näher drauf.
Die wollen im Grunde in ihrem Modul Herr über ihre Datenstruktur sein,
die sie verwenden und kein anderer soll wissen, dass sie die benutzen.
Und an dieser Stelle spricht man dann auch davon, dass man starke
Kohesion innerhalb eines Moduls hat.
Also alles, was in einem Modul passiert, das hängt stark zusammen,
stark voneinander ab.
Und andersrum, was zwischen Modulen passiert, das sollte eine schwache
Kohesion haben.
Und jetzt kommt der entscheidende Punkt.
Bauen Sie Ihre Module so, dass man sie für sich gesehen verstehen
kann.
Das spricht eben wieder genau gegen diese genannten Gottklassen.
Sie sollen ein riesiges Modul haben, was einfach alles macht.
Und das heißt doing things and stuff.
Und dann gucken Sie da drauf, dann wissen Sie einfach nicht, was das
tut.
Das heißt, nehmen Sie da die richtige Granularität und fassen Sie
Funktionalität zusammen, die auch wirklich zusammengehört.
So, und jetzt kommen wir zu einer schönen Definition, die jetzt so ein
bisschen die Folge ist aus all dem, was ich Ihnen jetzt gerade eben
erzählt habe.
So, Definitionen kann man einfach mal auswendig lernen.
Und zwar nicht nur deshalb, weil man dann in der möglichen Prüfung gut
dasteht, wenn Professor Tichy fragt, sagen Sie mir doch mal, wie die
Definition vom Modul ist, sondern auch deshalb, weil die meistens
irgendwie dann doch Sinn machen.
Und die Definition vom Modul sagt jetzt ganz einfach, ein Modul ist
eine Menge von Programmelementen, die nach dem Geheimnisprinzip
gemeinsam entworfen und geändert werden.
Da stehen jetzt zwei Dinge drin, mit denen wir uns ein bisschen
genauer beschäftigen müssen.
Das sind zum einen mal die Programmelemente.
Naja, das sind jetzt einfach die Typen Klassenkonstanten, Variablen,
Datenstrukturen, alles, was Sie eben so haben, Funktion und so weiter
und so fort.
Und das andere ist dieser ominöse Begriff Geheimnisprinzip.
Wir hatten das letztens schon in der Übung mal so kurz angerissen,
aber wahrscheinlich hat das dann nicht allen von Ihnen etwas gesagt.
Und deshalb schauen wir uns das jetzt mal ein bisschen im Detail an.
Was ist dieses Geheimnisprinzip?
Das Geheimnisprinzip besagt, dass jedes Modul eine wichtige
Entwurfsentscheidung hinter einer wohl definierten Schnittstelle
verbirgt, die sich bei einer Änderung der Entscheidung nicht
mitändert.
Und dieses Prinzip ist wirklich essentiell.
Das kam irgendwann in den 70er Jahren auf, als man sich damit
beschäftigt hat, wie kann man denn jetzt mal Software sinnvoll bauen?
Und da war das wirklich ein großer Durchbruch, weil es war so die
große Erleuchtung, dass das vielleicht der Trick sein könnte, wie
Software dann jetzt doch sinnvoll sein kann.
Und ich glaube, ich erkläre Ihnen jetzt am besten an einem Beispiel,
was denn das Geheimnisprinzip tatsächlich ist und warum es denn
sinnvoll ist.
Hier unten steht schon kurz aufgeführt, nur was verborgen und
unbenutzt ist, kann ohne Risiko geändert werden.
Und das ist genau der Punkt.
Das ist genau das, was Sie erreichen wollen.
Sie wollen Ihr Modul ändern können, ohne dass es Einfluss hat auf die
Benutzung dieses Moduls.
Und wie gesagt, das schauen wir uns jetzt mal kurz an dem Beispiel an.
Wir haben hier eine Klasse, die heißt Strecke ohne Geheimnis.
Die hat offensichtlich sich nicht an das Geheimnisprinzip gehalten.
Und dort kann ich einfach in den Startpunkt X Wert reingreifen, in den
Startpunkt Y Wert reingreifen.
Das ist alles public.
Und ich habe Zugriff auf alle Datenstrukturen in meiner
Streckenimplementierung.
Das heißt also, wer auch immer dieses Objekt benutzt, der muss wissen,
wie das da drinnen intern aussieht.
Wer von Ihnen weiß denn jetzt, was das Geheimnisprinzip an der Stelle
verlangen würde?
Ihr Kommilitone sagt, das Geheimnisprinzip sagt so viel wie, naja, wir
machen jetzt mal diese Attribute, die machen wir jetzt mal private.
Und dann bauen wir Getter und Setter dazu.
Und dann haben wir das wunderbar gekapselt und haben das erledigt.
Und schauen wir mal, das ist genau das, was hier auf der nächsten
Folie steht.
Jetzt haben wir eben genau das erreicht, was wir wollten.
Wir können nicht mehr direkt in die Datenstruktur reingreifen.
Wir können nicht mehr auf den Startpunkt X Wert zugreifen.
Wir können nicht mehr auf den Startpunkt Y Wert direkt zugreifen.
Wir haben das gekapselt über Getter und Setter Methoden.
Jetzt ist aber die Frage, wie sieht es denn jetzt mit der Semantik
aus?
Ist die Semantik jetzt wirklich gekapselt?
Genügt das jetzt schon, um das Geheimnisprinzip zu erfüllen?
Ihr Kommilitone fragt, gilt es auch für nicht änderbare Werte?
Die Antwort ist ein ganz klares Jein.
Manchmal wollen sie eben Zugriff haben auf Konstanten.
Ja, das gibt es natürlich.
Aber sie müssen sich dann immer darüber bewusst sein, dass Leute sich
darauf verlassen, dass diese Konstanten da sind.
Das heißt, wenn sie dann später mal was an ihrem Modul ändern und
sagen, die Konstante gilt nicht mehr und die lasse ich jetzt mal weg,
dann ist die halt weg.
Dann funktioniert eben vielleicht einiges nicht mehr bei Leuten, die
ihre Bibliothek verwenden.
Also ja, das kann man machen.
Aber wie gesagt, für Konstanten ja, das war ja ihre Frage.
Aber wie gesagt, seien sich über die Folgen davon bewusst.
Im Allgemeinen gilt Geheimnisprinzip, Attribute private, insbesondere
die, die sich ändern können.
Gut, das war aber nicht auf das, was ich hinaus wollte, sondern ich
wollte auf etwas anderes hinaus.
Wie sieht es denn mit der semantischen Kapselung aus?
Überlegen Sie sich mal, was wir hier machen wollen.
Wir haben hier eine Klasse, die eine Strecke repräsentieren soll.
Wir haben jetzt Getter für Startwert X und Getter Startwert Y und
jeweils Zetter dazu und das gleiche auch noch für den Endpunkt.
Ist das sinnvoll?
Ihre Kommilitonin sagt, sinnvoller wäre es gleich, einen ganzen Punkt
zurückzugeben.
Und das ist genau das, was man tun sollte.
X- und Y-Wert gehört bei einem Punkt zusammen.
Das heißt, Sie bauen sich an der Stelle eine zweite Punktklasse, bauen
die auch schön sinnvoll nach dem Geheimnisprinzip auf, mit Gettern und
Zettern und dann haben Sie die Möglichkeit, eben auf Ihre Startpunkte
zuzugreifen und auch da wieder die X- und Y-Werte herauszuholen.
Aber Sie haben dann den Vorteil, dass in Ihrer Streckenklasse das so
gebaut ist, dass Sie tatsächlich immer ganze Punkte zurückbekommen und
ganze Punkte setzen können.
Und jetzt ist die Frage, warum ist das denn jetzt besser?
Warum ist das denn besser, als die X- und Y-Werte einzeln zu setzen?
Das ist doch viel einfacher.
Habe ich eine Klasse weniger?
Muss ich weniger bauen?
Ihr Kommiliton hat die Folien vom letzten Jahr durchgeblättert,
deshalb weiß er, was auf der nächsten Folie kommt.
Oder er ist einfach nur tatsächlich sehr kreativ.
Worauf ich an der Stelle eigentlich noch vorher hinaus wollte, also
die Antwort ist natürlich vollkommen richtig, ist, Sie könnten ja den
Fall haben, dass Sie Ihre X-Koordinate ändern und die Y-Koordinate
verwenden und schon ist es inkonsistent, was Sie da getan haben.
Das kann Ihnen nicht passieren, wenn Sie einmal atomar den Punkt
ändern.
Dann haben Sie den entweder komplett geändert oder eben nicht.
Genau deshalb möchte man das haben.
Gut, jetzt zu der anderen Sache.
Warum ist das Geheimnisprinzip denn jetzt tatsächlich wirklich richtig
wichtig?
Naja, wir wollen eben Dinge, die sich ändern können, hinter unserer
Schnittstelle verbergen.
Das heißt, wenn Sie jetzt auf die Idee kommen, ah, die Idee mit den
zwei Punkten zur Darstellung der Strecke war doch eine blöde Idee, ich
mache das jetzt mal mit Länge und Winkel, dann ändert sich hier an
Ihrer Schnittstelle nichts.
Von außen können Sie das Modul immer noch genauso benutzen.
Sie können immer noch get-end-Punkt und set-end-Punkt benutzen, wie
vorher auch.
Aber intern wird das jetzt eben über Länge und Winkel berechnet.
Von außen merken Sie das aber gar nicht.
Das ist der entscheidende Punkt am Geheimnisprinzip.
Ungefähr klar geworden, warum das richtig und wichtig ist?
Das ist jetzt der Zeitpunkt, wo Sie alle nicken sollten.
Ah, sehr schön.
Ja, brav.
Sehr gut.
Also Geheimnisprinzip, immer dran denken.
Wir werden Sie zwingen, daran zu denken, in der einen oder anderen
Übungs -Lausuraufgabe.
Also wägen Sie das jetzt mal unter wichtig ab.
Gut, was bedeutet das jetzt?
Ich habe das nochmal zusammengefasst.
Voraussehbare Änderungen sollten ohne Schnittstellenänderungen möglich
sein.
Das ist genau das, was wir gerade gesehen haben.
Dass man mal sagt, wir wollen die Strecke anders implementieren.
Das kann ja mal passieren.
Aber dann sollte man das eben von außen nicht sehen.
Und bei weniger voraussagbaren Änderungen, dann kann sich natürlich
auch mal eine Schnittstelle ändern.
Manchmal hat man einfach den Entwurf nicht richtig gemacht, bei der
Schnittstelle was vergessen, kann vorkommen.
Aber das sollte eben nur dann passieren, wenn die Änderung nicht
voraussehbar war.
Also wenn Sie vorher wissen, das könnte ich auch nochmal anders
implementieren, dann berücksichtigen Sie das gleich beim Entwurf.
Gut, hier nochmal die Anmerkung, die verkürzte Form des
Geheimnisprinzips.
Wir bauen private Attribute und Eta und Zeta dazu.
Das ist aber, wie gesagt, und das haben wir gerade sehr schön gesehen
am Beispiel, nur die notwendige Bedingung.
Das ist aber nicht die hinreichende Bedingung.
Dafür ist noch ein bisschen mehr Semantik nötig.
Die zweite Anmerkung ist jetzt, was bedeutet denn eigentlich dieses
Duplicated in Java?
In diesem Zusammenhang jetzt hier.
Weiß das jemand?
Ihr Kommilitone sagt genau das Richtige.
Das wird jetzt gerade noch unterstützt, aber in Zukunft vielleicht
nicht mehr.
Und das macht man genau in so einem Fall, dass man sagt, die
Schnittstelle, die ich da mal vor 15 Jahren entworfen habe, die war
damals okay, aber mir ist aufgefallen, da müsste ich jetzt dann doch
mal was ändern.
Dann setzt man eben eine Methode auf Duplicated, gibt noch den
Hinweis, benutzt stattdessen lieber Methode XY und dann kann es halt
sein, dass in der nächsten Version die alte nicht mehr da ist.
Das heißt also, wenn Ihnen das mal über den Weg läuft und Sie eine
Methode benutzen wollen aus einer Bibliothek und die ist Duplicated,
dann schauen Sie sich mal um, ob Sie nicht vielleicht eine bessere
andere Lösung finden.
Weil dann kann es gut sein, dass bei der nächsten Version der
Bibliothek diese Variante nicht mehr möglich ist.
Gut, wie komme ich denn jetzt eigentlich zu einem sinnvollen Entwurf,
mit dem ich mich an das Geheimnisprinzip halte?
Wir waren gerade beim Geheimnisprinzip und der Frage, wie
berücksichtige ich das denn jetzt im Entwurf?
Am besten damit, dass ich einfach mal meine Entwurfsentscheidungen
aufschreibe und mal so eine Liste anfertige von
Entwurfsentscheidungen, die besonders schwierig sind und solche, die
sich voraussichtlich ändern werden.
Also denken Sie wieder an das Beispiel mit der Strecke.
Das ist so ein Klassiker, da könnte man sich überlegen, das ist was,
wenn ich das entwerfe, merke ich schon, da könnte sich an der
Implementierung mal was ändern.
Und wenn Sie solche Dinge identifiziert haben, dann sollten Sie eben
genau jede solche Entwurfsentscheidung hinter einem Modul und einer
entsprechenden Modulschnittstelle verbergen.
Was sind jetzt so Klassiker, die man verbergen möchte?
Naja, ich hatte schon gesagt, Datenstrukturen.
Ob Sie eine Liste verwenden oder ein Array, um gewisse Daten zu
erhalten, das sollte einfach von außen nicht sichtbar sein.
Das gleiche gilt auch für maschinennahe Details.
Wenn Sie irgendwie Treiberzugriffe haben, kapseln Sie das weg.
Betriebssystemnahe Details genauso.
Wenn Sie irgendwie Zugriff auf Ihr Betriebssystem bauen in Ihrem Modul
und Sie wollen das vielleicht mal austauschen, weil Ihr Software aus
welchen Gründen auch immer auf einem Mac laufen soll, dann kapseln Sie
das einfach weg und dann ist alles, was Sie für den Windows-Zugriff
gebaut haben in einem Modul und alles, was Sie für den Mac-Zugriff
haben in dem anderen.
Was von außen aber gleich funktioniert.
Das gleiche gilt auch für Datenbanken.
Wenn Sie meinen, MySQL ist das Beste überhaupt und verwenden das und
überall gibt es direkten Zugriff daraus aus allen Modulen und dann
läuft aber Ihre Lizenz aus, zum Beispiel, war jetzt ein schlechtes
Beispiel, Oracle wäre vielleicht besser gewesen.
Okay, Sie haben eine Oracle-Lizenz teuer gekauft, weil das ist total
geil, aber dann sagt zwei Wochen später Ihr Chef, die können wir uns
nicht mehr leisten, wir gehen pleite.
Und Sie haben aber Ihre Software so gebaut, dass alles direkt darauf
zugreift, dann ist das einfach eine blöde Idee.
Also sowas sollte man auch wegkapseln.
Gut, was haben wir noch?
Einen Ausgabeformat, auch das sollte man kapseln.
Schnittstellen, stellen Sie sich vor, Sie wollen eine Schnittstelle
für den Benutzer bauen, machen Sie ja eigentlich fast immer, dann
möchten Sie das eben kapseln.
Sie wollen ein GUI-Modul haben und Sie möchten ein Kommandozeilen
-Modul haben.
Wenn Sie eine Software benutzen, die mit Nutzern interagiert, dann
haben Sie normalerweise auch irgendwie Dialoge und die sind heutzutage
nicht mehr auf einer Sprache, nicht nur auf Englisch, sondern Sie
werden das vielleicht auch auf Deutsch, Französisch, was auch immer
vertreiben wollen und dann möchten Sie im Grunde alles, was
sprachabhängig ist, eben auch kapseln.
Jetzt sind wir also soweit, dass wir Entwurf und Dokumentation der
Modulstruktur haben können.
Das heißt, wir haben jetzt eben den Modulführer als Entwurf und in
diesem Modulführer wird beschrieben einmal die Entwurfsentscheidung,
im Grunde das Geheimnis, dass jedes Subsystem kapselt und natürlich
die Funktion des jeweiligen Moduls und des Subsystems und ganz wichtig
auch die Gliederung Ihres Gesamtsystems.
Und warum ist jetzt so ein ordentlich geführter Modulführer wichtig?
Naja, wenn wir das sinnvoll machen, dann vermeiden wir Duplikationen
und das ist deshalb wichtig, weil alle Duplikate, die Sie haben, die
müssen Sie pflegen.
Sie müssen immer Änderungen in beiden Duplikaten vornehmen.
Sie haben keine Lücken.
Wenn Sie also feststellen, Sie haben eine Dokumentation geschrieben
und keiner versteht die, wenn Entwickler XY nicht da ist, dann weiß
der vielleicht was, was in dem Modulführer nicht drinsteht.
Sie haben das ganze System zerlegt und damit verstehbar gemacht und
was auch ganz wichtig ist für die langfristige Sicht, wenn Sie Ihre
Software mal warten müssen und das wird Sie wahrscheinlich treffen,
dann finden Sie eben auch schneller Module, die betroffen sein könnten
von gewissen Fehlern, die auftreten.
Gut, hier steht nochmal was zu den Modul-Schnittstellen.
Was ist dort im Entwurf festgehalten?
Naja, im Grunde genau dieses Blackbox-Verhalten, von dem ich schon
gesprochen habe.
Sie wissen nicht, wie es innen drin funktionieren muss.
Also das sollte dokumentiert sein in der Modul-Schnittstelle, sondern
Sie wissen nur, wie man es benutzt.
Gut, was ist da so gelistet?
Naja, die öffentlichen Programmelemente, Parameter, Rückgabewerte,
Beschreibung des Effekts, was denn jetzt gerade dieser Teil tut, was
dieses Modul tut, welche Ausnahmen werden ausgelöst und so weiter und
so fort.
Wen erinnert das jetzt an etwas, was er vielleicht schon mal gesehen
hat?
Er sagt, ein Kommilitone, das erinnert stark an einen Javadoc und das
ist genau das Richtige.
Dort versucht man eben genau diese Dokumentation zu erreichen, um eine
genaue Schnittstellenartig zu beschreiben, was denn jetzt in der
Methode passiert, was in der Klasse passiert, dokumentiert eben genau
diese Dinge.
Das heißt also, wenn Sie dann später mal ordentlich implementieren,
Javadoc -Kommentare hinterlassen in Ihrem Code, dann kriegen Sie einen
Großteil davon einfach schon geschenkt.
Weiß jemand, was ein Quick-Index ist?
Quick steht für Keyword in Kontext.
Hat schon mal jemand gehört?
Dann haben Sie wahrscheinlich nicht so viel mit Büchern aus den 70er
-Jahren zu tun.
70er-Jahre kam ja schon bei Sebastian heute zweimal vor.
Sie sehen, dieser ganze Foliensatz beschäftigt sich mit der
Vergangenheit, die aber immer noch wichtig ist.
Worum geht's?
Sie kennen das vielleicht aus Büchern, hinten dran ist so ein Index,
da stehen dann irgendwelche Wörter und auf welcher Seite Sie das Ganze
vielleicht finden.
Das Ganze hat man auch in den 70er-Jahren schon gebraucht, da hatten
wir nicht nur einzelne Wörter, sondern ganze Titel.
Damals noch nicht, aber sowas wie Harry Potter und der Stein der
Weißen oder James Bond oder irgend sowas.
Also nicht nur ein Wort, sondern ganze Titel.
Und die wollte man hinten in so einem Index haben, in alphabetischer
Reihenfolge.
Und zwar alle möglichen Kombinationen.
Wenn Sie jetzt von Harry Potter nicht mehr wissen, dass der Vorname
Harry ist und Sie wissen aber, es war irgendwas mit Potter, dann
möchten Sie unter dem Wort Potter P auch den entsprechenden Titel
finden.
Das heißt, Sie möchten so eine zirkuläre Verschiebung von allen
Wörtern in diesem Titel haben.
Also Harry Potter und der Stein der Weißen, da möchten Sie auch unter
S wie Stein den kompletten Titel etwas verschoben finden.
Warum war das in den 70er-Jahren ein Problem?
Kann sich da jemand was vorstellen, wenn man jetzt irgendwie 10.000
Titel hat?
Man möchte jetzt so einen Index erstellen.
Ja, bitte.
Genau, das verbrauchte damals sehr, sehr viel Speicher.
Da hatte man halt irgendwie 600 Kilobyte Speicher für 10 Trilliarden
Euro oder D-Mark gekauft.
Das war zumindest ganz teuer.
Und wenn man dann halt mehr gebraucht hatte, kam ein Lastwagen und hat
ihm nochmal einen RAM-Riegel vorbeigebracht.
Für ebenfalls teuer Geld.
Das heißt, was Sie damals gemacht haben, naja, Sie kaufen sich einen
Computer für ein paar Millionen, kaufen für noch mehr Geld
Arbeitsspeicher, dann nimmt man lieber noch ein paar Informatiker oder
Ingenieure damals wahrscheinlich noch, die eben das Ding so bauen,
dass man halt möglichst wenig Speicher verbraucht.
Was ich Ihnen da noch zeigen will, ist dann ein Entwurf, den man
damals gemacht hat in dieser Fallstudie aus den 70er-Jahren.
Da haben Sie eben einen Entwurf gemacht aus Modulen und da möchte ich
mit Ihnen durchsprechen, ob der Entwurf so gut ist oder ob man da noch
was ändern könnte.
Nochmal genau worum ging es.
Man hat irgendwie eine gewisse Anzahl an Titel und möchte die jetzt
verschieben.
Das heißt, man möchte einmal unter A Harry Potter und der Orden des
Phoenix finden.
Man möchte aber auch unter P wie Potter, Potter und der Orden des
Phoenix, Harry finden können.
Was hier auch noch steht.
Naja, der, die das und und andere Füllwörter machen vielleicht keinen
Sinn.
Ich möchte nicht unter D der, der, der, der, der irgendwas stehen
sehen.
Das heißt, die Wörter möchte ich vielleicht überspringen.
Das heißt, wenn ich so eine zirkuläre Verschiebung mache, das erste
Wort wäre eins von diesen Wörtern, dann würde ich einfach nochmal ein
zweiter springen, weil das halt nicht sein muss.
Diese vier Beispiele hier sehen dann final so aus.
Wir müssen sie das Ganze lesen.
Dieses erste Wort der zirkulären Verschiebung ist hier jeweils groß
geschrieben.
Also die erste Zeile wäre sowas wie Flug des Phoenix, der.
Das heißt, wenn Sie wissen, okay, Harry Potter, bauen wir das mit
Phoenix, Phoenix.
Ja, dann gucke ich halt unter P und dann finde ich, ah okay, da ist
jetzt Harry Potter zu finden.
Das heißt, man muss irgendwie das so hinbekommen, dass es permutiert
wird und danach muss das Ganze eben noch alphabetisch geordnet werden.
Hier ist nochmal ein weiteres Beispiel, was Ihnen eigentlich nicht
viel Neues erklärt.
Abs, Integer, Absolute, Value.
Wenn ich das jetzt so permutieren will, hätte ich jetzt halt vier
verschiedene Möglichkeiten.
Dann käme unten das hier alphabetisch geordnet raus.
Erst Abs, dann Absolut, Integer und Value.
Dieses Beispiel werden Sie gleich auf den weiteren Folien sehen.
Den Entwurf, den Sie damals gemacht haben, möchte ich Ihnen mal
vorstellen.
Der sieht so aus.
Wir haben ganz oben einen Treiber, der hat nur eine Aufgabe.
Der ruft einfach in der richtigen Reihenfolge die anderen Module auf.
Jetzt gibt es ein Eingabemodul.
Das fließt dann aus einer Textdatei eben die verschiedenen Titel
teilenweise ein.
Dann gibt es einen Zeilenspeicher.
Dann gibt es einen Verschieber.
Unser Verschieber macht eben diese Permutation.
Das heißt, er wechselt halt immer einen Wort durch.
Dann kommt der Sortierer, der am Schluss das Ganze dann noch
alphabetisch ordnet.
Und ein Ausgabemodul gibt es dann halt irgendwie.
Eine Kommandozeile aus oder wo auch immer man das haben will.
Der spannendere Teil ist dieser untere Teil.
Es gibt einen Zeilenspeicher, der das Ganze relativ performant
speichert.
Wir hatten ja schon gesagt, man muss schauen, dass man wenig
Speicherplatz benötigt.
Wir haben es dann mal so gelöst, dass wir einfach jeden Titel in eine
Zeile reingespeichert haben und dann danach sofort der nächste Titel
kam.
So konnten sie die kompletten Titel halt eben relativ performant oder
platzsparend im Arbeitsspeicher speichern.
Zusätzlich haben sie noch einen Verschiebeindexspeicher und einen
Sortierindexspeicher gemacht.
Sie können sich ja vorstellen, wenn sie jetzt eben diese Permutation
speichern wollen.
Heutzutage könnten sie sagen, naja gut, ich verschiebe das Ganze,
speicher es neu ab.
Ich verschiebe es nochmal, speicher es neu ab.
Ist ja heutzutage vielleicht egal, was Speicherplatz angeht.
Damals haben sie halt gesagt, naja, wir können es uns nicht leisten,
eben alles neu abzuspeichern.
Wir haben das bei dem Beispiel davor schon gesehen, dieses Absolute
Integer Value.
Wenn ich da alles speichern würde, dann hätte ich ja im Endeffekt
viermal genau dasselbe im Arbeitsspeicher stehen, nur etwas
verschoben.
Wie die das gelöst haben?
Die haben einfach Pointer gespeichert.
Der Pointer zeigt halt einfach auf das erste Wort der aktuellen
Verschiebung.
Dann müssen sie eben nur eine Liste von Pointern speichern und eben
nicht die ganzen Wörter nochmal.
Der Sortierindexspeicher, der macht dasselbe, auch nur eine Liste von
Pointern.
Dieser Pointer zeigt einfach auf die Verschiebung in alphabetischer
Reihenfolge.
Wir gucken uns das mal ein bisschen genauer an.
Oder vielleicht noch ganz kurz.
Sie sehen hier an diesen Pfeilen, die hängen sehr stark voneinander
ab.
Also dieses Ausgabemodul benötigt beispielsweise den Zeilenspeicher,
den Verschiebeindexspeicher und den Sortierindexspeicher.
Da könnte man natürlich darüber diskutieren, ob das so gut ist.
Das Ausgabemodul hatte ich schon angesprochen.
Liest halt eben das Ding ein und speichert es im Hauptspeicher einfach
hintereinander.
Und erzeugt einen Index, der eben angibt, wo jede Zeile beginnt.
Sieht man ein bisschen besser auf diese Grafik hier.
Das ist dieses Beispiel von der einen Folie vorher.
Die werden einfach hintereinander in den Arbeitsspeicher
reingeschrieben.
Und links sieht man diesen Zeilenindex.
Der sagt einfach, okay, der Titel I, der fängt einfach an der Stelle
an.
Und der nächste Titel I plus 1 würde jetzt zum Beispiel dort anfangen,
wo Punkt, Punkt, Punkt steht.
Also wenn ich jetzt noch einen zweiten Filmtitel hätte oder wie auch
immer, dann würde er jetzt bei I plus 1 stehen.
Dieses Backslash 0 ist einfach ein Zeichen, das angibt, okay, hier ist
die Zeichenkette zu Ende.
Damit man einfach per Format die ganzen Wörter hintereinander
speichern kann.
Also Sie sehen, dieser Zeilenindex hat eben die Größe von der Anzahl
dieser Titel, die Sie speichern wollen.
Für jeden Titel brauchen Sie einen Eintrag in diesem Zeilenindex.
Der Verschieber, der wird von diesem Treiber aufgerufen, nachdem die
Eingabe vollständig eingelesen und in den Hauptspeicher geschrieben
wurde.
Und der erzeugt jetzt auch so einen Index.
Allerdings muss der nicht nur sich merken, zu welcher Zeilennummer das
gehört.
Also die Zeilennummer entspricht jetzt so einem Titel.
Sondern der muss auch sagen, okay, wo ist jetzt die Adresse von dem
ersten Wort.
Auch hier haben wir den Vorteil, dass nicht alles neu gespeichert
werden muss.
Sieht man auch hier auf der Grafik ein bisschen besser.
Oben sehen Sie wieder den Zeilenindex, den wir schon besprochen
hatten.
Was jetzt dazu kommt, ist dieser Verschiebeindex.
Der speichert jetzt einfach jede einzelne Verschiebung ab.
Also zum Zeilenindex I, also sprich dieses Beispiel mit den vier
Wörtern, könnte man sagen, okay, die erste Verschiebung fängt bei Apps
an, die zweite Verschiebung fängt bei Integer an, die dritte bei
Absolute und die vierte bei Value.
Dann hätte man alle vier Möglichkeiten eben durch diese Pointer
gespeichert.
Das heißt, dieser Pointer würde sich immer den Zeilenindex sowie die
Verschiebung, also welches Wort an welcher Stelle, merken.
Auch hier relativ performant kann man sagen.
Das macht dann der Sortierer, das was der Name schon sagt.
Man muss das Ganze irgendwie noch in eine alphabetische Reihenfolge
bringen.
Wir haben jetzt hier unseren Index oder unsere Liste mit
Verschiebungen, die alle theoretisch möglich sind.
Jetzt möchte man die so ordnen, dass die Wörter, die davon
referenziert werden, dass die halt eben in alphabetischer Reihenfolge
sind.
Das heißt, man muss auf beiden Datenstrukturen arbeiten.
Ich brauche zum einen hier unten diesen Verschiebeindex, die muss ich
auslesen.
Zum anderen muss ich aber auch durch den Zeilenindex nachschauen, wo
beginnt das Ganze und was steckt da drin.
Was da gebildet wird, ist ein eindimensionales Feld, also einfach nur
ein Array beispielsweise.
In dem Array steht drin, okay, in alphabetischer Reihenfolge, in dem
Verschiebung 3, dann Verschiebung 5, dann Verschiebung 0, dann
Verschiebung 10.
Dann haben Sie eben nur die Pointer gespeichert, die eben auf diese
anderen Zeiger von dem Verschiebeindex zeigen.
Auch hier wiederum eine relativ glatt sparende und damals wirklich
gute Lösung.
Sieht dann grafisch so aus.
Sie haben hier beispielsweise die vier Dinge.
Sortierung an Indexstelle 0 zeigt auf diese erste Verschiebung, die
zeigt auf Apps.
Dann dieser zweite Sortierungseintrag zeigt auf diese dritte
Verschiebeindexstelle, die wiederum auf Absolut zeigt.
Dann kommen Sie auf Integer und dann kommen Sie auf Value.
Also so haben Sie relativ glatt sparend durch diese verschiedenen
Zeiger dafür gesorgt, dass das Ganze verschoben und alphabetisch
ausgegeben werden kann.
Ja, was macht das Ausgabemodul?
Ja, es braucht diese anderen drei Geschichten, die wir gerade gesehen
haben und drückt dann einfach so eine formatierte Ausgabe von diesen
zirkulären Verschiebungen oder auch immer aus auf der Konsole.
Überall hat man schon gesagt, ruft die Untermodule auf, kann sich um
Fehlermeldungen kümmern.
Ja, Schrittstellen haben wir nicht genau spezifiziert.
Was denken Sie von diesem Entwurf?
Ist das das Beste, was man haben kann?
Ist das, was man haben will?
Oder wüssten Sie, was man besser machen könnte oder was ein Problem
sein kann?
Also mein Kollege sagte, Sie sollten an der Stelle nicken, nicht
einnicken.
Ja, das ist ein Unterschied.
Also der Kommilitone meinte, der Treiber sei eine Gottklasse, dem
würde ich widersprechen.
Der Treiber an sich macht nicht viel, der orchestriert einfach nur.
Der ruft in jeder anderen Klasse das auf, das ist noch nicht das
Problem.
Das heißt, Sie würden die Fehlerbehandlung in die Untermodule
delegieren und nicht in den Treiber.
Okay, die Fehlerbehandlung, können wir uns nicht überlegen, ob das an
der richtigen Stelle ist, ja.
Aber darauf wollte ich noch nicht hinaus.
Das wäre dann noch ein anderes, größeres Problem.
Also ich hatte schon ein bisschen einen Hinweis gegeben mit den vielen
Pfeilen.
Als Tipp könnte das ein Problem sein.
Der Kollege schneidet schon mal mit einem imaginären Schwert diese
Pfeile durch.
Achso, mit dem ich gewunken habe, mit dem Lattenzaun.
Okay, ich hoffe, ich habe keinen Verletzten mit dem Lattenzaun.
Okay, aber dieses Schwert wäre nochmal ein Hinweis gewesen, ein
zweiter Lattenzaun.
Das Problem ist einfach, Sie haben sehr viele Abhängigkeiten.
Was passiert jetzt, wenn Sie die Art und Weise, wie Sie diese Pointer
speichern, ändern?
Welche Module werden jetzt davon betroffen?
Also sagen wir mal, Sie entscheiden sich jetzt anders, naja gut, wir
haben mehr Speicherplatz.
Ich möchte jetzt einfach jede Verschiebung einzeln speichern.
Ich möchte nicht mehr diese Pointer auf irgendwelche Verschiebeindizes
machen.
Was müssten Sie alles ändern?
Welche Module?
Also Sie müssten den Sortierer ändern, genau.
Den Verschieber müssten Sie ändern.
Müssten Sie die Ausgabe ändern?
Sie müssten auch die Ausgabe ändern.
Sie müssten sehr, sehr viel ändern.
Also Sie sehen, es hängt alles voneinander ab.
Da ist kein Geheimnisprinzip.
Da ist nicht, okay, ich habe irgendwelche wohldefinierten
Schnittstellen.
Und wenn dahinter jemand die Entwurfsentscheidung ändert, wie zum
Beispiel die Art und Weise, wie das Ganze gespeichert werden soll oder
verarbeitet werden soll, dann müsste ich jetzt hier überall
Anpassungen machen an jedem Modul.
Und genau das möchte man eigentlich nicht haben.
Wenn Sie sich jetzt mal überlegen, was sich alles ändern kann, naja,
wir speichern das Ganze gerade im Hauptspeicher.
Das muss ja jetzt nicht sein.
Man könnte das vielleicht auch auf Platte speichern.
Vielleicht ist dieser Index irgendwann mal so groß, dass wir das
einfach auf die Platte auslagern müssen.
Oder wir wollen eine andere Speicherwortgröße haben.
Wir wollen das in irgendeiner Form mischen.
Oder wir wollen Unicode-Zeichensatz unterstützen.
Irgendwelche Sonderzeichen, die es bisher nicht gab.
Oder wir wollen das Ganze als Listen von Wörtern speichern.
Oder wir haben uns noch nicht darum gekümmert, diese Wörter wie der,
die das überspringen.
Wie kann man das machen?
Das sind alles Dinge, die sich noch ändern können.
Die Speicherstruktur kann sich ändern.
Die Speicherung der Titel kann sich ändern.
Oder Sie ändern Ihre Entscheidung, diese Verschiebung als Index zu
speichern.
Das hatte ich schon erwähnt.
Man möchte das Ganze explizit erzeugen und jedes Mal neu speichern.
Das können Sie ja machen, wenn Sie mehr Platz haben.
Oder Sie sagen, die Sortierung möchte ich anders machen.
Ich habe gesagt, ich möchte alles alphabetisch sortiert haben.
Das braucht viel Zeit, vielleicht möchte ich das gar nicht mehr
machen.
Vielleicht möchte ich nur alles, was A hat, nach vorne schieben, aber
untereinander nicht mehr sortieren.
Das kann ich ja alles machen.
All das hätte Änderungen nach sich an so ziemlich allen Modulen.
Das möchte man nicht haben.
Das Einzige, was eigentlich okay wäre, wäre dieser untere Punkt.
Wenn ich nur die Eingabe- oder Ausgabeformate ändern will, dann reicht
es, wenn ich nur das Ein- oder Ausgabemodul ändere.
Das ist okay.
Aber bei allen anderen hier müsste man sehr viele Änderungen machen.
Darum präsentiere ich Ihnen noch einen alternativen Entwurf.
Das wäre dieser hier.
Sie sehen, da sind deutlich weniger Pfeile drin.
Das sehen wir hier.
Wir haben wieder den Treiber, der alles antreibt, der alles
orchestriert.
Wir sehen hier, dass die Ausgabe nur den Sortierer und den Verschieber
benutzt, aber beispielsweise nicht den Zeilenspeicher.
Sortierer benötigt nur noch den Verschieber.
Der Verschieber und die Eingabe benutzen den Zeilenspeicher.
Klar, die Eingabe schreibt rein, der Verschieber muss es ja irgendwie
auslesen.
Das heißt, wir haben hier aber keine Zyklen.
Und wir haben hier nicht mehr so die Abhängigkeit, wie wir es vorher
bei dem anderen Entwurf hatten.
Allerdings sollten wir uns jetzt mal noch ein bisschen die
Schnittstellen anschauen.
Wir hatten gesagt, oder auch in dem Entwurf haben wir einen
Zeilenspeicher, wo wir das Ganze speichern wollen.
Das heißt, wir brauchen hier auch irgendwelche Schnittstellen, die die
konkrete Logik dahinter kapseln.
Wie das hier umgesetzt wurde, ich lasse es mal offen, ob das jetzt die
beste Möglichkeit ist.
Man kann zeichenweise in diesen Zeilenspeicher reinschreiben.
Dazu gibt es diese Sätze-Zeichen-Prozedur, die einfach sagt, okay, in
der vierten Zeile, also Zeile 0 für nullten Titel, da möchte ich jetzt
das Wort an Stelle 0 schreiben und dort das Zeichen an Stelle 3
schreiben und da soll jetzt ein E rein.
Das könnte ich mit diesem Sätze-Zeichen machen.
Das heißt, in dem Fall könnte ich mit dieser Methode das Ding komplett
befüllen.
Ob das jetzt bequem ist, weiß ich nicht.
Dazu brauche ich dann aber auch noch so Hilfsmethoden wie Hole
-Zeichen, die genau dasselbe machen.
Da sage ich halt auch, welches Zeichen soll geholt werden.
Und dann brauche ich noch so etwas wie Hole-Die-Zeilen-Zahl oder Hole
-Die -Wörter-Zahl oder Hole-Die-Zeichen-Zahl.
Also wie viele Zeilen habe ich denn?
Wie viele Wörter habe ich denn pro Zeile?
Und in jedem Wort, wie viele Zeichen gibt es denn da?
Wenn man das hat, kann man da durchiterieren und kann sich auch alles,
was man in den Zeilenspeicher reingeschrieben hat, eben komplett
wieder rausholen.
Sie sehen hier ein paar Hinweise, Übungsaufgabe.
Wenn Sie Zeit und Lust haben, können Sie mal versuchen, das zu
implementieren.
Zumindest so ein bisschen abgewandelt, wie das hier steht.
Das ist eine gute Hilfe.
Sie sollten mit diesen Methoden hinkommen, mit diesen Prozeduren
hinkommen, die Sie da haben.
Ich hatte gesagt, Sie brauchen diese drei, Hole-Zeilen-Zahl, Hole
-Wörter -Zahl und Hole-Zeichen-Zahl.
Sie sehen damit, mit diesen drei Vorschleifen können Sie über alles
komplett drüber iterieren und sich alles holen.
Sie können sich aus jeder Zeile jedes Wort holen und von jedem Wort
eben jedes Zeichen.
Dann können Sie sagen, naja gut, wenn ich jetzt ein Wort vorbei habe,
dann mache ich meinetwegen so ein Leerzeichen rein.
Wenn ich so ein Index, so eine Zeile fertig habe, mache ich noch so
einen Zeilenumbruch.
Dann können Sie jetzt allein mit diesen beschachtelten Vorschleifen
alles auf der Konsole ausgeben.
Der Verschieber braucht auch so ähnliche Prozeduren.
Auch hier muss man sagen, naja, wie viele Verschiebungen gibt es denn
überhaupt?
Und hol mir die Zeichen, die zur Verschiebung L passen.
Das heißt, auch hier können Sie jetzt mit diesen Schnittstellen das
Ganze holen.
Wie das dahinter implementiert ist, wie das Ganze jetzt gemacht wird,
ob Sie die Verschiebung jetzt auf einer Platte, ob Sie Verschiebungen
jetzt einzeln gespeichert haben oder ob Sie jetzt immer noch diesen
Index im Hintergrund haben, der sich diese Verschiebung merkt.
Das sehen Sie jetzt hier dran nicht mehr.
Braucht Sie dann als Benutzer dieser Prozedur auch nicht mehr
interessieren.
Sie können sich darauf verlassen, dass Sie das eben so kriegen.
Und derjenige, der das anbietet, der hat halt die Freiheit, das noch
zu ändern nach dem Geheimnisprinzip.
Ja, der Verschieber braucht auch noch so ein paar Hilfsmethoden,
analog zum Zeilenspeicher.
Das ist nichts Neues.
Aber Sie sehen, dieser Verschieber kann eben, wenn er will, die
Verschiebung direkt erzeugen, muss er aber nicht.
Er kann speichern, wie er will.
Auch hier haben wir jetzt diese Stoppwörter, die weggemacht werden,
nicht betrachtet.
Wenn Sie möchten, überlegen Sie sich mal, wie Sie das machen können.
Der Sortierer, der benötigt natürlich auch noch irgendwie die
Funktionalität, dass er das Ganze sortieren kann.
Dazu gibt es hier diese Alphabetisierer, wer auch immer das übersetzt
hat, Methode.
Und der braucht dann natürlich noch so etwas wie, ja okay, dann gib
mir die IT-Verschiebung, dann kann ich die irgendwie ausgeben.
Aber auch hier hätten wir jetzt die Freiheit, die Sortierung kann
entweder alles auf einmal machen oder ich kann auf Bedarf sortieren
oder ich kann nur einen Teil sortieren.
Sehen Sie hier nicht mehr, das ist alles weggekapselt.
Die Eingabe und Ausgabe braucht natürlich so etwas wie Einlesen und
Verschiebung ausgeben.
Ich denke, da ist jetzt auch keine Magie dahinter.
Haben Sie noch Fragen zum Quick-Index?
Einfach mal, dass Sie gesehen haben, es gibt verschiedene Arten von
Entwürfen und worauf man achten kann und dass es Sinn macht, dass man
halt schöne Schnittstellen definiert, die man halt später noch ändern
kann und die halt eine gewisse Logik wegkapseln.
Zwei Folien würde ich noch machen, wenn ich darf.
Gut, ich verstehe, Sie sind alle Quick-Fans geworden.
Ein letztes Beispiel, das ich Ihnen noch zeigen will, ist die Java-AWT
-Component -Klasse.
Wer kennt die?
Ein Kollege kennt sie, wunderbar.
Also so ein paar wenige Hände habe ich gesehen.
Wenn Sie eine GUI unter Java machen wollen mit AWT, dann kennen Sie
diese Klasse vielleicht.
Bei einer GUI ist alles mit Komponenten gemacht.
Sie haben zum Beispiel ein Fenster und in das Fenster kommen Dinge
rein.
Zum Beispiel so ein Menü-Button oder eine Grafik oder alles Mögliche
wird einfach ineinander geschachtelt.
Das sind dann solche sogenannten Komponenten.
Diese Komponenten bieten, diese Component-Klasse bietet so ein paar
Dinge an, wie ich möchte eine neue Komponente hinzufügen, ich möchte
eine löschen oder ich möchte eine auslesen.
Wie das intern gespeichert wird, sehen Sie nicht, zumindest nicht an
der Schnittstelle.
Diese Klasse hat keine Attribute, die es nach außen gibt.
Das heißt, wenn Sie jetzt die Komponente an Stelle i haben wollen,
dann wissen Sie nicht, ob das als Feld oder Liste oder geordnete Menge
gespeichert wird.
Wollen Sie in dem Fall auch nicht wissen.
Und es kann auch sein, dass es irgendwann mal geändert wird.
Ich zeige Ihnen das mal nochmal grafisch.
Es gibt eine Container-Klasse, die erbt von diesem Component und die
bietet halt eben hier zum hinzufügen Add-Component.
Sie können auch sagen, an welcher Stelle Sie das Ganze haben wollen,
wenn Sie möchten.
Und dann auch Funktionalität zum Holen einer Komponente aus diesem
Container.
Können Sie ja auch alle als Array zurückgeben lassen.
Letztendlich können Sie das Ganze auch löschen.
Das hier ist ein Beispiel, wo die Funktionalität zur Verfügung steht,
aber die Implementierung weggekapselt ist.
Und zwar so, dass nachträglich diese Funktionalität so geändert werden
kann, dass eine andere Datenstruktur beispielsweise dahinter ist.
Damit übergebe ich an dich.
Ich habe da noch was.
Jetzt wollte ich gerade hier diese Übung unter den Tisch fallen
lassen, aber mein Kollege will, dass Sie noch eine Übung machen zu
Hause.
Wenn Sie möchten, schreiben Sie einen Iterator, ohne diese Get
-Components, die ein Array zurückliefert, Hilfsmethode zu verwenden.
Einfach mal, um zu sehen, wie Sie das Ganze nutzen können.
Sollte, glaube ich, nicht ganz so schwer sein.
So, dann bekomme ich auch noch ein bisschen Redezeit heute.
Nicht mehr allzu viel, aber mal schauen, wie viel wir noch schaffen.
Wir haben jetzt gesehen, Sebastian hat uns ein bisschen was darüber
erzählt, wie das mit dem modularen Entwurf umgesetzt wird, dass wir
diese Module erstellen wollen und wie wir die spezifizieren wollen.
Und wir haben jetzt auch zwei Beispiele gesehen von Martin.
Wie wird das Ganze denn jetzt, also der modulare Entwurf oder auch
Module, überhaupt in Programmiersprachen umgesetzt?
Na gut, wir sind jetzt erstmal noch in der nicht objektorientierten
Welt.
Das heißt so Sachen wie C, Ada oder Modular.
Diese Programmiersprachen bieten sprachliche Mittel an, mit denen man
solche Module definieren kann.
Wir haben ja eben schon darüber gesprochen, dass wir Module in Modul
-Schnittstellen und die eigentliche Implementierung dieser Module
zerlegen wollen.
Und genau das bieten diese Programmiersprachen an.
Das heißt, in der Schnittstelle sind halt alle öffentlichen und auch
nach außen hin sichtbaren Elemente dargestellt, sowie die konstanten
Typ -Deklarationen, Variablen, die nach außen hin sichtbar sind.
Und die Implementierung enthält dann alles Private und auch die
Umsetzung dieser Schnittstellen, die wir vorher gesehen haben.
Das heißt, die Schnittstellen von Modulen sind eigentlich nichts
anderes, als auch die Schnittstellen, die Sie schon aus Java oder
anderen objektorientierten Sprachen kennen.
Aber wie geht jetzt diese Trennung genau, zum Beispiel in C oder C++?
Ja gut, in C und C++.
Oder mal kurz die Frage, wer von Ihnen hat schon mal was mit C
programmiert?
Ah, das sind doch recht viele.
Gut, dann erzähle ich Ihnen leider nichts Neues.
Den anderen vielleicht schon.
In C gibt es die Möglichkeit, oder in C spezifiziert man die
Schnittstelle in sogenannten header-Dateien, die mit dem Punkt H
-Endung definiert werden.
In denen ist nur die Schnittstelle enthalten, die spezifiziert wird.
Und dann gibt es außerdem noch die Punkt C-Dateien, die die
eigentliche Implementierung enthalten.
Um jetzt für die Implementierung festzustellen, welche Schnittstellen
implementiert werden müssen oder auch zur Verfügung stehen müssen,
werden die Schnittstellen-Dateien eingebunden in die Implementierungs
-Dateien.
Das heißt, das macht man mit diesem include Annotation und wählt dabei
jetzt die Schnittstellen aus, die man in seine Implementierungs-Datei
einpflegen möchte.
Natürlich aber auch die Schnittstellen-Datei, die man selber
implementiert.
Das heißt, wenn man jetzt gerade die Implementierung vom Verschieber,
den wir eben schon gesehen haben, umsetzen möchte, muss man auch die
Schnittstellen -Datei des Verschiebers, Verschieber Punkt H,
einbinden.
Damit auch der Verschieber weiß, was überhaupt jetzt da implementiert
werden soll in dieser Klasse.
Das Ganze geht ohne eine Überprüfung der Namenspaarung.
Man sollte sich aber trotzdem daran halten, dass die Schnittstellen
-Datei dieselbe Benahmung hat wie auch die Implementierungs-Datei, um
nicht Verwirrung zu verursachen.
Man kann das aber auch anders machen und dann sollten Sie das gut
dokumentieren.
Der Übersetzer hat dann damit genug Informationen, um alles
umzusetzen, was er braucht.
Das heißt, er macht eigentlich bei diesem Include nichts anderes, als
die Schnittstellen-Definition in die Implementierungs-Datei einzufügen
und das Ganze dann weiter umzusetzen.
So sieht so eine Schnittstellen-Datei in C aus.
Das ist jetzt der Zeilenspeicher, den wir eben auch schon kurz gesehen
haben.
Das sind einfach ganz normale Schnittstellen-Definitionen, wie Sie sie
vielleicht auch so ähnlich aus Java kennen.
Das ist jetzt halt C-Code.
Wir haben jetzt hier so unsigned-Geschichten und so, die müssen Sie
jetzt aber nicht genauer verstehen.
Werden Sie noch im Betriebssystem genauer hören im nächsten Semester.
Hier nochmal eine weitere Schnittstellen-Datei.
Ich gehe da jetzt mal ein bisschen schneller drüber, weil wir nicht
mehr so viel Zeit haben.
Aber ich glaube, es sollte klar werden, dass hier ganz klar einfach
nur die Schnittstelle definiert ist.
Hier sind keine Implementierungs-Details enthalten und es sind auch
nur die nach außen hinsichtbaren Elemente in dieser Schnittstellen
-Datei.
Das heißt keine privaten Attribute, keine privaten Methoden, sondern
nur das, was nach außen hinsichtbar ist.
Jetzt schauen wir uns mal noch kurz an dem Beispiel von QUIC das
Importierungs -Geflecht an, was da rauskommt.
Wir haben jetzt hier in blau immer die Schnittstellen-Dateien
angegeben und in grün die Implementierungs-Dateien.
Und dieser Pfeil bedeutet jetzt, dass in der Implementierungs-Datei
vom Zeilenspeicher die Schnittstellen-Datei des Zeilenspeichers
importiert wird.
Wenn wir jetzt auch noch die Eingabeschnittstelle haben, dann haben
wir auch dazu wieder eine Implementierungs-Datei, die diese natürlich
benötigt, die Schnittstellen-Definition, die Eingabe natürlich
benötigt, aber auch zusätzlich die Schnittstellen-Datei des
Zeilenspeichers.
Und das geht dann halt so weiter.
Der Verschieber braucht außerdem auch die beiden Schnittstellen und so
weiter und so weiter.
Und das Hauptprogramm oder Treiber, den wir eben gesehen haben,
braucht dann natürlich in der Variante, in der unschönen Variante,
auch alle Schnittstellen-Dateien, um korrekt funktionieren zu können.
Ich habe noch zwei Minuten, also können wir noch ein bisschen
weitermachen.
Was ist jetzt der große Unterschied zwischen den Modulen an sich und
den Objekten, die wir später haben?
Naja gut, Module kann man nur einmal instanziieren.
Das heißt, wenn man ein Modul anlegt, gibt es dieses nur einmal.
Das liegt daran, dass Module noch ein bisschen früher in der
Entwicklung waren, als das mit den Klassen und Objekten eingeführt
wurde.
Das heißt, man hatte hier noch eine andere Denkweise, die zwar
ähnlich, aber noch zu einem früheren Punkt ist.
Und dort war es halt so festgelegt, dass man ein Modul nur einmal
instanziieren kann.
Das heißt, wenn man jetzt eine Punktklasse hat, ein Punktmodul hat,
kann man die Implementierung dieses Punktes nur einmal definieren.
Und wenn man jetzt einen zweiten Punkt haben möchte, muss man wiederum
ein weiteres Punktmodul anlegen und dieses irgendwie anders benennen.
Denn jedes Modul darf nur mit einem Namen einmal vorkommen.
Das ist natürlich nicht ganz das, was wir jetzt aus objektorientierten
Sprachen kennen.
Und es ist vielleicht auch für die Wartung nicht unbedingt immer
vorteilhaft, wenn man so eine Struktur hat.
Wenn man jetzt natürlich mehrere Punkte definieren will, also
verschiedene Instanzen von Punkten darstellen möchte, dann kann man
das mit Modulen natürlich auch machen.
Aber dann braucht man ein Modul, was zum Beispiel alle Punkte hält und
in sich alle Punkte drin hat.
Aber man macht keine einzelnen Instanzen dieses Moduls.
Das funktioniert nicht.
Dieses Problem ist dann in den objektorientierten Sprachen, wie wir
sie kennen, durch die Mehrfachinstanzierung oder auch
Mehrfachimplementierung gelöst.
Da kann man dann jetzt natürlich verschiedene Instanzen einer Klasse
anlegen und hat dann vielleicht x beliebige Punkte und muss dafür
nicht jedes Mal eine neue Klasse anlegen.
Hier oben habe ich glaube ich schon zum größten Teil gesagt, man kann
aber in objektorientierten Sprachen durchaus auch dafür sorgen, dass
manche Klassen nur einmal genannt werden bzw.
es nur eine Instanz einer bestimmten Klasse gibt.
Das geht über das Entwurfsmuster Einzelstück oder auch Singleton im
Englischen, was dafür sorgt, dass man von dieser Klasse nur eine
einzige Instanz anlegen kann.
Das geht dann über statische Attribute.
Und in objektorientierten Sprachen ist es natürlich außerdem möglich,
den gleichen Bezeichner für verschiedene Klassen zu verwenden, weil
man ja immer über die Objekte referenziert.
Das geht zum Beispiel in C oder in den anderen modularen Varianten
nicht, denn wie wir eben schon gesehen haben, brauchten wir für den
Verschieber eine VSHolezeichen und wir hatten für eine andere
Hauptklasse schon einen Holezeichenbezeichner für eine Methode.
Das heißt, da ging es nicht, dass wir jetzt zwei Holezeichenmethoden hatten.