Guten Morgen, ich hoffe, Sie sind jetzt alle aus der LA da.
Es ist schon wieder ein Rätsel am Schirm.
Schauen wir uns das kurz an.
Das ist nämlich ganz einfach.
Wir deklarieren eine Menge.
Set aus Kurzzahlen, Kurzzahlen, 16-Bit-Zahlen, NewHashSet, das stimmt
alles.
Übersetzen tut es sowieso.
Und dann haben wir einfach eine Schleife von eins von 0 bis 99
einschließlich.
Und wir setzen immer, das ist auch ein Short, wie man sieht,
diese Laufvariable, wir setzen diese Laufvariable ein in die Menge und
entfernen gleichzeitig eins davor.
Jetzt muss man sich erst überlegen, was ist im Falle 0?
Im Falle 0 würden wir minus 1 entfernen, das ist noch nicht drin,
passiert nichts.
Remove von etwas, das nicht da liegt, gibt keine Meldung.
Und dann machen wir weiter so, bis wir bei 99 sind.
In dem Schritt würden wir wohl 98 kurz danach entfernen.
Und am Schluss schauen wir uns an, wie lang, wie groß diese Menge ist.
S.3 es gibt uns die Größe der Menge.
Mögliche Antworten?
Eins, 100, Fehler oder nichts davon.
Was hier passiert ist, eigentlich das I-1 wird als Ganzzahl berechnet.
Sie entfernen also jetzt eine Ganzzahl und Remove sagt, Ganzzahl kenne
ich nicht.
Und macht weiter, kehrt zurück, ohne Meldung, ohne Meldung.
Es gibt gar keinen Fehler, es wird nicht mal ein Fehler ausgegeben.
Das heißt, die richtige Antwort ist 100.
Die Menge wird aufgebaut, das Entfernen klappt nicht, weil hier ein
Integer entfernt wird und die Menge hier enthält ja nur Shorts.
Das müssen wir uns vielleicht noch mal genau anschauen.
Das Dumme ist, dass nicht mal ein Fehler ausgelöst wird.
Das ist mal wieder so ein Konversionsproblem.
Hier entsteht ein Int, wird auch nichts entfernt.
Der Punkt ist, man kann durchaus dem Ding Ints geben und der Grund,
warum es sich nicht beklagt ist, dass das Remove nicht ein Short
erwartet, dann käme ja eine Konversion, eine automatische Konversion
in Betracht, sondern das Remove erfordert als Eingabe nur ein Object
und dann wird Int auf ein Int-Object konvertiert und dann sagt das
Ding, ja, nennen wir es nicht.
Also das Remove als Parameter hat ein Objekt und das ist der Grund für
dieses Problem.
Natürlich ist es ganz einfach zu korrigieren, indem wir hier einfach
das Ding wieder zurück umwandeln in ein Short.
Also bei den ganzen Collections, den ganzen Sammlungen in der Java
-Bibliothek, das gilt übrigens auch für Collection-Contains und Map
.get, wird immer ein Objekt erwartet und integrierte arithmetische
Operationen geben wir vom Typ Int oder Long zurück.
Wenn sie also schon bei Long sind, dann bleiben sie bei Long, wenn sie
bei Short sind, wird erst erweitert auf Int und dann gerechnet.
Daher, was wir hier lernen, ist Nummer eins, Remove nimmt ein Objekt.
Nummer zwei, Vorsicht mit gemischten Typen, mit Short und Long
gemischt.
Sicherer ist immer, sich die paar Bits zu gönnen für ein Int, anstelle
mit Shorts zu arbeiten.
Ich denke mit diesen kurzen Zahlen, mit den 16-Bit-Zahlen, die ja nur
bis 64.000 oder so 65.000 gehen, beziehungsweise nur 32.000 Werte
haben, negativ und positiv, sind nur, wenn sie wirklich
Speicherprobleme haben.
Wenn sie also auf einer Maschine sind, die sehr wenig Speicher hat,
auf ihrem PC mit diesem Riesenhauptspeicher, den sie wahrscheinlich
haben, ist es eigentlich kein Thema, den Platz zu sparen und dann
sparen sie sich komische Fehlermeldungen oder komische Ergebnisse.
In diesem Fall hatten wir nicht mal eine Fehlermeldung.
Und da hatte ich Ihnen etwas vorenthalten, nämlich die ersten zehn
Folien oder so und habe gleich mit der UML-Modellierung angefangen.
Das war bewusst so, weil ich erst Ihnen zeigen wollte, wie man in UML
-Klassen angibt, was ja sehr ähnlich ist.
Wir werden noch mehrere Ähnlichkeiten mit Java feststellen, aber nicht
identisch.
Aber jetzt ist die Frage, wir gehen nochmal zurück, was ist denn
eigentlich eine saubere Definition von Objekt und eine saubere
Definition von Klasse?
Was ist das Zeug eigentlich?
Und da habe ich jetzt ein paar Definitionen für Sie.
Die ersten sind wieder aus den einzelnen, ja gut, Sie sehen es gleich.
Ein Objekt bezeichnet ein Exemplar eines bestimmten Datentyps oder
einer bestimmten Klasse.
Das ist also eine Definition, die sehr eng an Programmiersprachen
angelehnt.
Ich suche aber nach einer allgemeineren Definition.
Unter einer Klasse versteht man ein abstraktes Modell für eine Reihe
von ähnlichen Objekten.
Das kommt der Sache schon näher.
Was wir machen, ist Folgendes.
Wir sagen einfach, wir definieren uns eine Grundmenge.
Ist überhaupt nicht kompliziert, sondern ich sage einfach, wir sagen
erst mal, es gäbe eine Grundmenge.
In dieser Menge können wir einzelne Elemente identifizieren, sofern
sie von anderen unterscheiden können, nennen wir sie ein Objekt und
Klassen bilden Mengen von Objekten, die etwas gemeinsam haben.
Jetzt warte ich noch, bis der mit seinem Text fertig ist.
Danke für Ihre Aufmerksamkeit.
Also, die Menge G, diese Grundmenge.
Menge ist eine Vereinigung aus allen vergangenen, gegenwärtigen und
zukünftigen, substanziellen und konzeptionellen.
Also wir substanziellen und konzeptionellen hatten wir schon gesehen,
hatte ich schon mehrmals erwähnt, dass es auch Dinge gibt, die wir uns
ausdenken, die wir auch als Elemente von irgendeiner Menge, nämlich
diese Grundmenge G betrachten, aber gegenwärtigen ist klar,
vergangenen, das es gar nicht mehr gibt, aber wir können damit auch
über die Vergangenheit sprechen und zukünftigen, Dinge, die wir noch
gar nicht gesehen haben.
Das ist sehr viel weiter gefasst, als was wir vorhin auf der Folie
hatten, nämlich ein Exemplar eines bestimmten Datentyps.
Den Datentyp weiß ich noch gar nicht, was das ist.
Erst wenn ich in der Programmiersprache bin, weiß ich, was ein
Datentyp ist.
Beispielsweise enthält diese Menge G also Personen, mich, sie, unsere
Tutoren, Luft, substanziell, mein Lebensversicherungsvertrag,
Demokratie, konzeptionell, das kann man nicht anfassen, ganz klar, das
ist ein Konzept, Fußball Europameisterschaft 2012, das ist ein
Ereignis, das ist also auch konzeptionell, Schwimmen ist eine
Tätigkeit, konzeptionell, Schwimmen kann ich auch als Fähigkeit
betrachten, kannst du schwimmen?
Die Fähigkeit zu schwimmen und die Tätigkeit zu schwimmen kann ich
unterscheiden.
Ich habe eine blaue Farbe, konzeptionell, blaue Farbe, kann ich die
irgendwo anpacken?
Ja, wohl nicht, ja und so weiter.
Das ist erstmal unsere Grundmenge von allem, was man konzeptionell und
substanziell hat, oder auch in der Zukunft das haben wird und dass wir
natürlich nicht wissen, was das sein wird.
Gut, dann haben wir so eine riesige, unvorstellbar große, unendliche
Menge von allen Sternen des Universums, allen Atomen.
So, wir treffen uns jetzt an ein Objekt und zwar ist es ein
Individuum, mindestens ein Individuum, erkennbares, eindeutig von
anderen Objekten unterscheidbares, also bestimmbares Element.
Also diese Luft hier kann ich nicht bestimmen, die entgleitet mir, ich
könnte höchstens sagen, die Luft hier in diesem Raum, dann kann ich es
eingrenzen, aber ich muss also ein Element unterscheidbar bestimmen
können aus dieser Menge.
Dann spreche ich von einem Objekt.
So und dann ist also diese Menge Omega der aller Objekte, die
unterscheidbar sind von anderen, identifizierbar, nenne ich Omega.
Das ist jetzt wieder eine Teilmenge und zwar eine echte Teilmenge, es
gibt genügend Zeug, das wir nicht identifizieren können.
Es gibt es noch ein paar andere Definitionen, die sagen, alles was man
mit einem Nomen oder Namen bezeichnen kann, sei ein Objekt.
Das ist schon ein Objekt, denn ich kann es irgendwie durch den Namen
identifizieren, aber es reicht nicht.
Es gibt natürlich Sachen, Objekte, die ich mit keinem Namen belegt
habe und daher existieren sie als Objekte und sind unterscheidbar,
obwohl sie keinen Namen haben.
Bei einem Object, I mean anything that we can think, anything we can
talk about, naja, also da habe ich wieder eher ein Problem damit.
Das ist vielleicht mehr allgemein, denn es sagt nicht mehr etwas aus
über ein identifizierbares Objekt.
Also wir benutzen diese Definition, dass wir eine Grundmenge haben von
allem konzeptionalen, substanziellem und wenn es darin einzelne
Elemente gibt, die identifizierbar sind, dass ich sie feststellen
kann, ich habe das eins, ich habe eins, ich kann es beschreiben, dann
gehört das zur Menge Omega der Objekte, das sind erstmal meine
Objekte.
Und was ist jetzt die Klasse?
Eine Klasse ist einfach eine willkürliche Kategorie, Untermenge dieser
Menge Omega.
Und diese Kategorie kommt von dem Wort Prädikare, nämlich Aussagen
oder Angeben.
Ich brauche natürlich eine Beschreibung dieser Kategorie.
Ich könnte also sagen, alle Menschen, alle Menschen älter als zehn
Jahre, ist auch eine Kategorie, eine Subkategorie der Menschen zum
Beispiel.
Ich brauche also eine Unterscheidung.
Es kann natürlich auch sein, dass wir eine Kategorie angeben, die es
nicht gibt, Menschen über 150 Jahre.
Ist natürlich leider eine leere Kategorie, aber es ist klar, dass
damit durch diese Kategorisierung eine Gleichheit, Ähnlichkeit von
Objekten beschrieben wird.
Alle verfügen über diese Eigenschaft, mit denen ich sie der Klasse
zufügen kann.
Okay, das war die Klasse und ein Exemplar oder eine, in
Programmiersprachen würde man dann schon von Instanz sprechen, aber
wir sprechen hier noch allgemein, wir sind noch nicht bei einer
Programmiersprache.
Ein konkretes Element aus einer bestimmten Klasse, also ein Exemplar
gehört zu mindestens einer Klasse.
Es muss eine Klasse geben, so es gehört.
Es kann aber sein, dass es zu mehreren Klassen gehört.
Es könnte zu einer Oberklasse gehören, aber auch noch zu einer
Unterklasse und davon eine Unterklasse und davon eine Unterklasse.
Also Menschen größer als 1,80 Meter zum Beispiel gehören natürlich
auch zur Klasse Menschen und die Klasse Menschen gehört zur Klasse
Säugetiere und die Säugetiere gehören zur Ordnung irgendwas, ich weiß
es nicht, was es ist, in der ganzen biologischen Hierarchie hoch.
Also es ist klar, dass einzelne Exemplare auch gerne zu mehreren
Klassen in der Klassenhierarchie gehören können.
Es kann auch noch sein, das habe ich gleich auf der nächsten Folie,
Moment, es kann auch noch sein, dass es Überlappungen von Klassen
gibt, aber das bespreche ich gleich.
Das möchte man meistens vermeiden, aber manchmal ist die Natur ein
bisschen anders.
So, wenn man jetzt Programmiersprachen und UML-Dokumente und so Zeug
alles liest, dann wirbeln diese Begriffe durcheinander.
Entität, Objekt, Exemplar, Klasse, Instanz, Typ.
Typ haben wir auch gerade vorhin gesehen.
Was ist das eigentlich?
Ja, ich hatte schon gesagt, der Typ ist eigentlich dann, wenn wir das
Wort Typ benutzen, wir für eine, meistens für eine Klasse, aber auch
für primitive Typen, wenn wir von Programmiersprachen reden,
objektorientierten Code zum Beispiel.
Von einer Instanz sprechen wir, wenn wir wieder vom objektorientierten
Code sprechen, das sind die Exemplare in unserer Programmiersprache,
aber auch im Modell, auch da spricht man von Instanzen, Klassen, das
Gleiche, an den gleichen Einordnungen wie die Instanzen, das ist
wenigstens typisch.
Das Wort Exemplar nimmt man meistens nur im Modell her und eigentlich
nicht im objektorientierten Code und natürlich auch in der
Wirklichkeit ein Exemplar.
Sie und ich sind ein Exemplar aus der Menge Homo sapiens.
Objekt kommt überall vor und Entität in den oberen beiden.
Also die Trennung der Begriffe ist immer ein bisschen durcheinander.
Ich möchte nur darauf jetzt hinweisen, dass wir in der Vorlesung
einfach die Definition so wählen, wie wir sie gesehen haben und die
Vermischung vermeiden.
In der Analysephase wird oft auch ein Objekt benutzt, gewissermaßen
als Repräsentanten für eine ganze Klasse.
Das kommt eben manchmal vor, führt aber auch ein bisschen zur
Verwirrung.
Zum Beispiel könnte ich das hier, diese Menge hier als die Restmenge
bei Division durch Ganzzahl, Division durch 4 bezeichnen, aller
Zahlen.
Wir wenden also den Begriff Klasse und Instanz dann, wenn die Struktur
der Klasse identifiziert und festgelegt ist, Struktur oder das
Prädikat, mit dem ich Tasten zugehörig teste und ein Element aus genau
dieser Klasse gemeint ist, also auf der Realisierungsebene.
Als nächstes, wir haben das natürlich in der UML-Deklaration der
Klassen letztes Mal schon gesehen, müssen wir noch über Attribute
sprechen.
Also ein für alle Exemplare einer Klasse, ein für alle Exemplare einer
Klasse definiert die vorhandene Eigenschaft.
Das ist jetzt, Sie denken jetzt natürlich wahrscheinlich an Variablen,
aber wir sind noch nicht in der Programmiersprachenwelt, sondern wir
sagen einfach irgendeine Eigenschaft, die vorhanden ist.
In UML können wir wieder nur bestimmte Eigenschaften angeben.
Für das einzelne Exemplar, unabhängig von den anderen angegeben, einen
klar definierten Wert hat und jetzt in UML wieder für alle die gleiche
Domäne hat.
Wenn ich also einen Attributnamen habe, wie zum Beispiel Alter, dann
würde ich sagen, das hat einen Typ, Ganzzahl und unter Umständen einen
Wert, der gleich oder unterschiedlich sein kann, aber der Typ ist für
alle gleich und der Name.
So, also zum Beispiel, ich könnte ein Exemplar haben, dass die
Attributfarbe ist rot und angebissen, ja, ein Exemplar zwei, die Farbe
grün, angebissen, nein, rot und nicht angebissen und grün und
angebissen.
Das sind vier unterschiedliche Exemplare der Klasse Apfel.
Wir müssen jetzt noch unterscheiden, Attribut und Instanzvariable
eines Objektes.
Häufig ist es so, dass ich Attribute, wie zum Beispiel die Farbe oder
die Eigenschaft angebissen, abbilden kann auf eine Instanzvariable.
Die heißen in Java Instanzvariablen.
Selten spricht man davon, Attributen.
Man muss jetzt immer UML, also die ganz große Menge von allem
Substanziellen und Konzeptionellen, dann die Menge der Objekte und
dann die Menge der Objekte, die in UML beschreibbar sind, anschauen.
Also, wenn Sie ein Attribut haben, in UML haben, dann ist das
normalerweise abgebildet auf eine Instanzvariable, Farbe, angebissene
Eigenschaft und so.
Wenn Sie eine Instanzvariable aber haben, heißt das nicht unbedingt,
dass das ein Attribut ist.
Deswegen hier die Umkehrung nicht, denn es kann sein, dass diese
Attribute, Entschuldigung, diese Instanzvariablen wirklich eine
Assoziation kodieren.
Sie müssen ja, Sie haben ja in Java keine Assoziationssachen, Sie
müssen sie simulieren oder implementieren als Instanzvariablen.
Also die Instanzvariablen, das ist ganz einfach, können sowohl
Attribute beinhalten, als auch die Verknüpfungen, die sich aus den
Assoziationen ergeben.
Zusätzlich wäre es manchmal noch interessant, bestimmte
Einschränkungen oder Zusicherungen für diese Attribute zu geben.
Zum Beispiel, dass ein bestimmtes Attribut nie negativ werden kann
oder was weiß ich, immer größer 0 sein kann, aber nicht größer als 100
oder auch Bedingungen, die mehrere Attribute verknüpfen untereinander.
Zum Beispiel, wenn ich ein Attribut hätte, das mein Alter angibt, aber
auch ein Attribut, das mein Geburtsdatum angibt, dann ist da natürlich
eine Bedingung, die sagt, diese beiden Dinge müssen konsistent sein.
Wenn also das Geburtsdatum 2000 ist, dann kann das Alter nicht mehr
als 16 sein.
Diese Zusicherungen, da gibt es eine zusätzliche Sprache, das heißt
die Object Constraint Language, die werden wir aber nicht behandeln,
die bewahren wir uns auch für Software Technik 2.
Das macht uns das Leben ein bisschen einfacher.
So, dann spricht man oft von Objektidentität und wenn sie gleich sind
und das müssen wir uns nun genau anschauen, nämlich die Gleichheit von
Objekten.
Man spricht also, die Existenz eines Objektes ist unabhängig von
seinen Attributwerten, ob es jetzt grün ist oder rot ist oder
angebissen ist oder nicht, existiert natürlich das Objekt unabhängig
davon und zwei Objekte, die die gleichen Attribute und die gleichen
Attributwerte haben, sind auch unterscheidbar, sie sind nicht
dieselben Objekte.
Ich kann also zwei rote Äpfel, die angebissen sind, sein oder drei
davon, aber sie sind als Objekte unterscheidbar.
Also 22 können gleich sein und dasselbe zu sein und das führt uns
jetzt zu dem Begriff der Gleichheit und der ist auch ein bisschen
kompliziert, aber nicht allzu schlimm.
Und zwar gibt es da Gleichheit von einer mehrstufigen Gleichheit,
deswegen Gleichheit x-ter Stufe und die Gleichheit 0-ter Stufe sagt,
ich habe keine unterschiedlichen Objekte, die zwei Objekte, deren
Gleichheit ich prüfen würde, ergibt, dass sie identisch sind.
Also ich habe vielleicht, wenn ich es konkret mache, wenn ich zwei
Verweise auf diesen Apfel habe, möchte ich prüfen, ob das jetzt der
gleiche, exakt dasselbe Apfel ist oder nur einer, der ihm gleicht.
Und Gleichheit 0-ter Stufe heißt Identität, das ist genau das Gleiche.
Ein Objekt und keine zwei.
So, Gleichheit 1-ter Stufe sagt jetzt folgendes, entweder handelt es
sich um dasselbe Objekt, also Gleichheit 0-ter Stufe, wenn das aber
nicht der Fall ist, dann sollten die beiden Objekte in allen
Attributen identische Werte besitzen.
Da müssen wir jetzt aufpassen.
Also diese drei Äpfel hier sind gleich in der ersten Stufe.
Sie sind in diesem Fall, diese drei sind nicht gleich in 0-ter Stufe,
aber sie sind gleich in ihren Attributen.
Und zwar müssen jetzt die Attribute identisch sein und die Werte hier
Rot, Rot, Rot sind tatsächlich identisch.
Interessanter ist es dann allerdings, wenn ich auch noch andere ganze
Objekte drin habe, also nicht nur diese primitiven Datentypen wie die
Farbe oder das Alter oder sowas, sondern vielleicht eine ganze Person
eingepackt habe in dieses Objekt, dann sage ich, dann für diese
Gleichheit 1-ter Stufe, dass sich die Objekte ähneln, müssen dann aber
auch die Verweise auf die eingebetteten Objekte identisch sein, das
identische Objekt sein.
Das ist dann die erste Stufe und jetzt können Sie sich gut vorstellen,
das kann ich aber noch weiter treiben, könnte gewissermaßen mehr Stufe
gehen, da könnte ich jetzt sagen, die Gleichheit 2-ter Stufe ist so,
es handelt sich entweder um dasselbe Objekt, also Gleichheit 0-ter
Stufe, ist also eingeschlossen bei 2-ter Stufe oder es handelt sich um
zwei verschiedene Objekte, die aber in allen Attributen gleiche oder
identische Werte auflösen.
Identische Werte war Gleichheit 1-ter Stufe, gleiche Werte heißt
wieder, ich muss die einzelnen Objekte anschauen, die dort eingebettet
sind und prüfen, ob sie gleich sind, was heißt entweder identisch oder
gleiche Attributwerte.
Das heißt, es geht dann nur noch drei Stufen weit.
Also zum Beispiel, wenn ich jetzt die gleichen Äpfel von vorhin, die
ja nur gleich waren, nicht identisch, in eine Menge einpacken würde,
zum Beispiel in eine Tüte und dann zwei solcher Tüten vergleichen
würde, dann würde ich nach Gleichheit 2-ter Stufe fragen.
Da frage ich, sind die Tüten gleich, das heißt also, wären sie erstmal
schon identisch, dann wäre die Sache schon abgehakt, aber wenn sie
nicht identisch sind, dann schaue ich mir die Attribute an.
Wenn die Attribute gleich sind, entschuldige, die Attribute identisch
sind, dann ist Gleichheit 1-ter Klasse erfüllt, als 1-ter Stufe, wenn
sie aber nicht identisch sind, aber Verweise auf die anderen, auf die
Äpfel zu gleichen Äpfeln führen, dann habe ich auch Gleichheit 2-ter
Stufe erreicht.
Sie können sich das vorstellen, dass ich immer überlegen muss, wie
viele Schritte von Verweisen muss ich eigentlich gehen, bis ich zur
Identität komme.
Irgendwann mal muss Sachen identisch sein, denn sonst kann ich nie von
Gleichheit sprechen.
Aber das Objekt selbst muss nicht identisch sein, dass ich vergleiche,
ich möchte nur wissen, ist es gleich.
Führt natürlich gerne zur Verwirrung und natürlich muss ich auch
Assoziationen und Zustand, das noch später kommt, hinzufügen.
Zustand ist ganz einfach, kommt in den nächsten Folien, was Zustand
ist, aber Assoziationen, die zeigen mir natürlich, dass ich auf
Objekte verweise und deren Gleichheit muss ich prüfen.
Wir haben zwei unterschiedliche Äpfel, die aber auf den gleichen Wurm
verweisen.
In der realen Welt ist das schwierig, aber wir können es so
konstruieren, das wäre Gleichheit 1-ter Stufe, denn die Attribute
dieser Wurm sind gleich, sind identisch.
Wenn die Würmer nur der gleiche Typ von Wurm ist, aber unterschiedlich
Würmer, dann haben wir Gleichheit 2-ter Stufe.
Ja,
noch Fragen dazu?
Ja,
also gut, da gibt es jetzt keine Definition, die ich Ihnen bieten
könnte, aber da sagt man genau das, was Sie sagen.
Ich habe zwei Äpfel, deren Position oder deren Alter oder Farbe oder
sowas gleich ist.
Aber da kann man nicht mehr sagen, dass die Äpfel gleich sind, ganz
gleich sind, sondern nur aufgrund dieses einen Attributs oder einer
Submenge von Attributen gleich.
Aber da gibt es dann keine speziellen Überlegungen mehr darüber.
Ja, natürlich, es gibt n-te Stufe.
Sie ist da nicht mehr besonders nützlich, aber sie ist trotzdem
interessant, wenn Sie zum Beispiel vernetzte Datenstrukturen haben,
zwei unterschiedliche vernetzte Datenstrukturen, sagen wir einen
Graphen und noch einen Graphen und wollen jetzt sagen, sind das
eigentlich die gleichen Graphen.
Und da brauche ich natürlich, solange es keine Zyklen gibt, mal von
den Zyklen abgesehen, in diesen Graphen suche ich dann natürlich nach
Gleichheit bis zur Länge des längsten Pfades im Graphen.
Oder anders ausgedrückt, manchmal prüfen wir auf Gleichheit, man
unterscheidet neben da eben diese sogenannte seichte Gleichheit und
die tiefe Gleichheit.
Die seichte Gleichheit sagt, ist das Zeug identisch, ist es das
gleiche Objekt.
Oder auch noch die Gleichheit erster Stufe, die Objekte sind zwar
nicht identisch, nicht das gleiche Objekt, es sind zwei verschiedene
Objekte, aber sie sind in den Attributen gleich, aber ihre
Assoziationen, ihre Verknüpfungen zu anderen interessieren mich nicht
mehr.
Dann spricht man von dieser seichten Unterscheidung.
Auch beim Kopieren ist es das Gleiche, auch da will man natürlich
wissen, mache ich eine seichte Kopie oder eine tiefe Kopie.
Das heißt also, angenommen Sie haben ein Objekt, eine Person, die
Person ist verknüpft mit einem Ehepartner, ist irgendwo angestellt,
hat mehrere Verknüpfungen, woanders hin und dann sagt man, so und
jetzt kloniere dieses Objekt.
Das heißt also, eine Kopie davon erzeugen.
Jetzt ist die Frage, wird jetzt der Ehepartner und die Firma auch
kloniert oder nicht.
In dem einen Fall hängt natürlich davon ab, was das Problem ist, aber
in dem einen Fall, wo man nur das Eingangsobjekt, diese Person,
einschließlich aller seiner Werte kopiert, hat man eine seichte Kopie
angelegt und die ist dann gleich in erster Stufe und dann haben Sie
zwei Personen mit dem gleichen Ehepartner.
Das wäre natürlich von der Modellierung her dumm, aber da muss man
darauf achten, beim Kopieren, dass man tiefe Kopien macht und alles
doppelt anlegt.
Noch Fragen dazu?
Wie gesagt, wichtig bei vernetzten Datenstrukturen, dass man sagen
kann, wie tief man geht.
Gleicher dritter Stufe geht dann weiter und jetzt brauchen wir noch
eine Definition, die ein bisschen abweicht von dem, was Sie
wahrscheinlich gehört haben oder vielleicht noch hören werden.
Das ist die Eigenschaft des Zustands.
Was ist ein Zustand eines Objektes?
Häufig sagt man, der Zustand eines Objektes ist gegeben durch die
Anzahl der Bits, die ich brauche, um das Objekt zu repräsentieren.
Alle Bits, die in dem Objekt vorkommen, ist der Zustand.
Das ist die Sichtweise, sagen wir, des Ingenieurs, der die Maschine
baut.
Der sagt, ich brauche 64 Bit für dieses Objekt und das nenne ich
seinen Zustand.
Es kann aber sein, dass unter den 64 Bit oder noch mehr manche Bits
dazukommen, die nicht zur Zustandseigenschaft in unserem Sinne
beitragen.
Also Beispiel.
Angenommen, wir hätten so eine Ampel und für die Ampel, den Zustand
der Ampel, der ist natürlich interessant, ob es rot ist oder gelb ist
oder grün ist oder gelb-rot ist.
Aber ich habe vielleicht noch ein weiteres Attribut, das sagt, was die
Betriebsstunden, sagen wir, der Glühlampe da drin ist.
Die Höhe der Betriebsstunden ist jetzt für den Zustand der Ampel, wenn
Sie auf sie zufahren und sie ist grün, ist völlig unerheblich und auch
das Umschalten der Ampel von Grün auf Gelb, auf Rot oder so, ist auch
völlig unerheblich.
Sie brauchen zwar Bits, um die Betriebsstunden festzuhalten, aber die
Betriebsstunden ändern nichts am Verhalten der Ampel.
Die Betriebsstunden allein.
Die Birnen können natürlich ausfallen, das ist jetzt eine andere
Situation, aber die Betriebsstunden an sich tun nichts am Verhalten
der Ampel ändern.
Die Betriebsstunden ist irgendwie ein Zähler, der sagt, wie lange die
Ampel im Betrieb ist, wie viele Tage, Wochen, Monate, keine Ahnung.
Es ist keine Konstante, ändert sich.
So, was wir also machen, um diesem Problem zu entgehen, dass wir
zusätzlich Information speichern, die zum Zustand nichts beiträgt,
sagen wir folgendes.
Der Zustand oder englisch State, solange sich ein Objekt in einem
Zustand befindet, in einem Zustand, reagiert es im gleichen
Verwendungs - oder Aufrufskontext immer gleich auf seine Umwelt,
reagiert immer gleich in einem bestimmten Zustand, nie anders, ändert
sich der Zustand, reagiert das Objekt in mindestens einem Kontext
anders.
Also nehmen wir wieder die Ampel an, wenn sie im Zustand grün ist und
ich sage nächster Zustand, also schalten der Ampel weiter, gehe ich
immer vom Grün, ich weiß nicht, was kommt zuerst auf das Gelb und vom
Gelb geht es dann weiter auf Rot.
Wenn ich in Rot bin, ist der nächste Zustand Grün oder Rot-Gelb und
dann Grün.
Seltsam, dass man das gar nicht weiß, wo man täglich an der Ampel
vorbeikommt.
Ja, aber sehen Sie, die Betriebsstunden oder die Temperatur oder so
hat nichts damit zu tun.
Oder das Wetter, ob es gerade hagelt, ist egal.
Aber wir sagen also, in einem gegebenen Zustand regiert das Objekt auf
die Operationen, die ich daran ausführe, immer gleich und wenn ich in
einem anderen Zustand bin, muss ich mindestens eine Situation haben,
in der sich das Objekt anders verhält.
Das ist bei der Ampel ganz leicht, denn es wechselt ja immer in einer
bestimmten Folge von Farben auf andere Farben.
Okay.
Ja, das UML hat da eine andere Vorstellung davon, a condition or
situation during the life of an object, during which it satisfies some
condition, performs some activity or waits for some event.
Ne, gefällt mir nicht.
Was hat der Zustand mit dem Warten zu tun?
Also wir verwenden diese Definition.
So, jetzt ist es so, dass ich natürlich, wenn ich jetzt ein UML
-Diagramm habe mit einem Zustand drin, dann brauche ich natürlich
Instanzvariable, um den Zustand festzuhalten.
Ich kann den aber direkt festhalten, zum Beispiel dedizierte Variablen
dazu benutzen oder aber einen impliziten Zustand zu haben, nämlich
indem ich sie aus den vorhandenen ausrechne.
Zum Beispiel könnte ich die Wahlberechtigung aus dem Geburtsdatum
herauslesen.
Wenn ich die Wahlberechtigung habe, dann verhalte ich mich anders, als
wenn ich sie nicht habe.
Und das Prädikat, dass man sagt, bist du jetzt Wahlberechtigter oder
nicht, naja, das sehe ich eben nach, wie alt die entsprechende Person
ist.
Das ist also einfach ganz einfach.
Jetzt sind wir schon fast durch mit diesen generellen Prinzipien.
Das Kapselungsprinzip, das haben Sie ja auch schon mit Java
programmieren ja auch eigentlich voll gelernt, muss ich also nicht
mehr dazu versagen.
Der Zustand kann zwar nach außen hin sichtbar sein, der Zustand, sagen
wir, der Ampel ist natürlich nach außen hin sichtbar, es gibt auch
verborgenen Zustand, aber meistens ist es sichtbar.
Aber wir erlauben nicht, dass jemand den Zustand der Ampel von außen
irgendwie mit einem Knopf ändert.
Sie können zwar sagen, ich möchte jetzt überqueren, aber die Ampel
entscheidet, wann sie auf grün schaltet.
Ja genau, das ist eigentlich der Hauptpunkt, dass er vom Objekt selbst
verwaltet wird und möglichst nicht überschrieben werden kann von
außen.
Zum Beispiel, um es als etwas krasses Beispiel zu nehmen, wenn Sie
einen Videorekorder oder sonstigen Rekorder haben, würden Sie nicht im
Leben auf die Idee kommen, das Gehäuse aufzuschreiben, um da innen
drin irgendwo einen Kurzschluss zu erzeugen, damit das Ding in Zustand
spielen geht.
Sondern Sie haben die vier, die drei Zustände, Nichtstun, Abspielen
und Warten und die Übergänge machen Sie, indem Sie auf die
entsprechenden Knöpfe drücken, was nichts anderes ist, als die
Operation auf dem Gerät auszurufen.
Das sagt zum Beispiel vom Nichtstun in den Zustand Abspielen zu gehen
und dann natürlich auch abzuspielen.
Okay, natürlich komme ich mit Pause auf Warten und mit Play komme ich
von Warten auf Abspielen zurück und da fehlt noch ein Übergang, wenn
ich im Warten auf Stop drücken würde, dann würde ich in den Nichtstun
-Zustand zurückgehen.
Ziemlich einfach.
Oft spricht man von einem Nachrichtenaustausch zwischen Objekten, das
ist nichts weiter als ein Methodenaufruf, aber man möchte betonen,
dass ein bestimmtes Objekt der Nachrichtenempfänger aufgefordert wird,
einen Zustandsübergang durchzuführen, also der Aufruf in den Zustand
Abspielen.
Das ist der Aufruf, die Nachricht von außen.
Aber sonst ist es implementiert natürlich nur wie ein ganz normaler
Methodenaufruf.
Okay, so.
Gut, das haben wir schon gesagt.
Wiedergabe oder Abspielen.
Methoden.
Methoden können natürlich den Zustand eines Objekts verändern, sie
können auch etwas ausrechnen, das ist klar, aber was den Zustand
betrifft, verändern sie unter Umständen den Zustand.
Und die verfügbaren Methoden zur Zustandsänderung definieren dann auch
die zulässigen Zustandsänderungen, die durch die Botschaften, die man
schicken kann.
Wann ist jetzt ein Zustandsübergang erlaubt und wann nicht?
Es kann ja sein, dass ich zum Beispiel im Zustand Nichtstun nicht
sofort in den Zustand Pause wechseln kann.
Und das spezifiziert man mit Zustandsübergangsdiagrammen, die kennen
sie.
Wir werden noch mal die Zustandsübergangsdiagramme aufgreifen,
nächstes Mal wahrscheinlich.
Da werden wir dann sogenannte Harrell-Automaten benutzen.
Das sind auch nichts anderes als Zustandsübergangsautomaten mit
einigen Kurzschreibweisen, die es billig machen, auch komplexe
Automaten auf ein Blatt Papier zu bringen.
Aber keine Angst, das ist nichts Neues, Automaten kennen sie und wir
haben hier also nur einfach Zustände und Übergänge.
Da muss man dann natürlich entscheiden, was passiert, wenn ein
unerlaubter Zustandsübergang gefordert wird.
Wenn ich also, was weiß ich, Zustandsübergang von Rot direkt auf Grün
möchte, ohne erst über Gelb zu gehen.
Was passiert dann?
Naja, dann kann ich ein Ausnahmeobjekt erzeugen, eine Ausnahme
auslösen, ich kann eine Fehlerinstanz erzeugen, ich kann Unsinn
bewirken oder ich kann es ignorieren.
In den Zustandsautomaten, die wir dann haben, werden wir dann auch für
die unmöglichen oder unerwünschten Übergänge sagen müssen, was dann zu
tun ist.
Zustandsautomaten werden oft Kanten haben, für Eingaben, die keine
Kanten haben, für Eingaben, die im Prinzip gegeben sein könnten.
Da muss man sagen, ob man in einen Fehlzustand geht oder nicht und
wenn man das hat, dann kommt man, wenn man das direkt schreibt, dann
kommt man meistens mit Automatenbildern, die furchtbar aussehen, weil
von allen Kanten irgendwie Zeugs weggeht, das nicht erlaubt ist.
Und da gibt es auch wieder eine Kurzschreibweise, die die Automaten
übersichtlich hält.
Okay, so das Nächste kennen wir schon, die Methodensignatur.
Man spricht davon, der Methodensignatur oder den Methodenkopf in der
in der UML erscheinen also nur der Methodenname, die Rückgabetyp,
falls es einen gibt und die Parameterliste, das nennt man die
Signatur.
Das ist in allen Programmiersprachen auch so, dann nennt man auch den
Kopf einer Methode in allen Programmiersprachen seine Signatur.
Die Parameter sind also die Nutzerdaten, die ich dem Objekt mit
übergebe.
Übrigens, wenn ich so etwas hinschreibe wie O, also an Objekt Punkt M,
einen Methodenaufruf an einem Objekt O, dann kann ich das auch als
eine und wird auch so angesehen als ein spezieller Funktionsaufruf,
wobei das O eigentlich der nullte Parameter ist.
Also O Punkt M, Klammer auf, Parameter, ist eigentlich der Aufruf M,
Klammer auf, O, Parameter.
Das objektorientierte Schreibweise ist zwar bequem, aber dieses O
Punkt vorne ist eigentlich nur eine Kurzschreibweise oder eine andere
Schreibweise, die betonen soll, welches Objekt man ansprechen will.
Im Prinzip ist es aber nur der nullte Parameter.
So wird übrigens C++ auch implementiert.
C++, objektorientierte Sprache, wenn Sie da einen objektorientierten
Objektaufruf machen, wird der umgewandelt in diese Form.
Notation hatten wir nur zur Erinnerung, Methodenname zuerst,
Parameterliste, wie wir sie kennen.
Der Rückgabetyp hinten, dummerweise nicht das gleiche wie Java,
Attributnamen hat auch ein Typ, der Typ steht auch hier hinten, der
Parameterliste kommt das vor und da kann man auch Beere angeben.
So, gut, soweit also jetzt diese Vorgeschichte, die ich jetzt nach
diese UML-Syntax setzen wollte.
Gibt es noch Fragen dazu, zu den allgemeinen Konzepten?
Dann gehen wir gleich weiter, mit was es noch alles an Syntax in UML
-Klassendiagrammen gibt.
Wir hatten gesagt, das was wirklich neu ist für Sie, was Sie in der
Programmiersprache wie Java nicht kennen, sind die Assoziationen.
Dazu möchte ich nochmal darauf betonen, das ist ein Grund für häufige
Verwirrungen in Übungsaufgaben als auch in Klausuren.
Machen Sie eine klare Unterscheidung im Kopf zwischen einer
Assoziation und den Verknüpfungen mit den Objekten.
Die Assoziation wird angegeben zwischen Klassen.
Klassen sind keine Objekte, haben wir ja schon gehört.
Die Objekte enthalten dann die Verbindungen.
Die Assoziationen geben uns nur an, die potenziellen Verknüpfungen
zwischen Objekten.
Das ist gewissermaßen eine Typerklärung.
Wir hatten zum Beispiel die Assoziation beschäftigt, Sie erinnern
sich, eine Firma beschäftigt Personen.
Die Assoziation hier ist keine Verknüpfung, sondern die Verknüpfung
ist zwischen Instanzen von Firmen und Instanzen von Personen oder
Exemplaren.
Also hier zum Beispiel habe ich eine Firma F1, eine Person P1, P2 und
P3 und da gibt es diese Verknüpfung.
Das heißt, die F1 hat Personen P1 bis P3 angestellt und diese Firma
hier, die keinen Namen hat, Sie sehen, ich kann das so notieren durch
Unterstreichung, hat nur eine Person.
So, dann haben wir gesagt, die Kardinalität, die Kardinalität zum
Beispiel hier, diese Kardinalität an dieser Assoziation, null bis
beliebig viele, könnte ja auch nur Stern alleine sein übrigens, heißt,
diese Menge von Verknüpfungen für eine beliebige Firma, eigentlich für
jede Firma, die es in der Modellierung gibt, muss der Bedingung null
bis beliebig viele erfüllen, was natürlich drei locker erfüllen kann.
Das ist eigentlich überhaupt keine Beschränkung, das kann gar nicht
sein, aber beliebig viele, ja.
Aber wenn hier drei Stünde, dann müsste das immer drei sein und das
wäre fehlerhaft, weil da nur eine 1 ist.
Sie erinnern sich, die Definition, wie interpretiert man eigentlich
diese Kardinalitäten ist, da wo sie dran stehen, zur
gegenüberliegenden Seite zu gehen, sich dort ein Objekt auszuwählen,
nämlich das hier, alle seine Verknüpfungen zu Instanzen dieser Klasse
angeben und dann die Größe dieser Menge zu betrachten.
Zum Beispiel hier, heißt zum Beispiel, wenn man uns diese Kardinalität
anschaut, die ist jetzt natürlich eingeschränkt, die sagt jetzt, auf
die gegenüberliegende Seite gehen, Personeninstanzen nehmen und
nachsehen, wie viele Verknüpfungen die Einzelperson zu einer
beliebigen Firma hat.
Das ist in diesem Fall immer eins, ja.
Wir könnten noch eine fünfte Person angeben, um sie keiner Firma zu
ordnen, dann ist die Kardinalität auch erfüllt, denn es darf auch 0
sein.
Also immer daran denken, ich muss auf die andere Seite gehen und dort
zählen, wenn es um die Assoziation geht.
So, das war jetzt das eine, aber nochmal zurück, das Wichtige war mir,
Assoziation ist eine Information über Klassen, wird für Klassen
angegeben, die gibt mir Informationen darüber, was die Instanzen für
Verknüpfungen haben dürfen.
Die tatsächlichen Beziehungen gibt es natürlich nur zwischen Instanzen
und nicht zwischen Klassen.
Okay, so, also hier noch mal die Interpretation einer Assoziation, in
diesem Fall einer binären Assoziation.
Wir sagen, eine Firma beschäftigt, das bekomme ich aus der
Leserichtung, aus diesem kleinen Pfeil, beschäftigt null bis beliebig
viele Personen, verschiedene Personen, weil da steht noch Unique, das
heißt, es ist, sofern man möchte, natürlich erlaubt, die gleiche
Instanz mehrmals zu verknüpfen.
Das nennt man dann einen sogenannten Multigraphen.
Also das, was dabei herauskommt, ist ein Multigraph.
Also ich könnte zum Beispiel, ich gehe noch eins zurück, hier eine
zweite Verknüpfung einfügen, wenn hier nicht Unique steht.
Hier steht hier nicht, also hier wäre es erlaubt.
Aber hier beliebig verschiedene, das ist eine Restriktion, Personen
als Angestellte.
Das Wort, das uns fehlt, ist noch der Arbeitgeber, da könnte ich nur
so beschäftigen, eine Firma beschäftigt als Arbeitgeber null bis
beliebig viele verschiedene Personen als Angestellte.
Und ein Passiv geht natürlich auch, das mache ich jetzt nicht mehr
nochmal.
Klassenattribute, das ist nichts anderes als statische Attribute in
Java.
Ausführlich erklärt ist, man kann jetzt und das bringt jetzt alles
durcheinander, vorher hatten wir noch schön Klasse und Objekte
getrennt, jetzt sagen wir, jetzt können wir aber auch noch Klassen von
Exemplaren des Typs Klasse sehen.
Lassen Sie sich davon nicht durcheinander bringen, aber wir können den
Klassen selbst auch Attribute geben, nämlich irgendwelche Zählmarken
zum Beispiel.
Alles klar?
Und Methoden unabhängig von den Attributen definieren.
Diese Dinge heißen Klassenattribute beziehungsweise Klassenmethoden.
In Java sind die einfach durch Static markiert und zum Beispiel in der
Klasse Math gibt es statische Attribute E und P zum Beispiel und die
statischen Methoden Square Root und Cosinus mit entsprechenden
Parametern und so weiter.
Die statischen Attribute und Methoden werden durch Unterstreichen
gekennzeichnet.
Das Ergebnis ist, wenn Sie jetzt von Math eine Instanz bilden würden,
erscheinen selbstverständlich diese statischen Attribute ebenfalls als
zugänglich.
Sie können Sie über dieses Objekt ansprechen, aber es gibt zum
Beispiel von denen immer nur eine einzige Variable.
Wenn ich also zum Beispiel hier eine Variable einführen würde, zum
Beispiel wie viele Instanzen von Math habe ich erzeugt und das immer
aktualisiere, dann wird jede Zuweisung, jede Instanz, die ich davon
erzeuge, immer auf die gleiche Variable, auf das gleiche Attribut,
nämlich das Klassenattribut zugreifen.
Jetzt schreiben oder lesen.
Aber das kennen Sie, statische in Java ist Static und das brauche ich
zum Beispiel bei Methoden, die nicht an eine bestimmte Klasse als
Objekt gebunden sind.
Zum Beispiel die Quadratwurzel aus einer Zahl, wo ist das Objekt?
Also es gibt es nicht dazu, sondern ich habe alles erledigt, indem ich
den Parameter übergebe.
Das ist ziemlich einfach.
Konstruktoren, Konstruktoren müssen wir auch noch definieren,
Konstruktoren werden in UML leider anders behandelt.
Sie können Konstruktoren einen beliebigen Namen geben, muss nicht der
Klassenname sein, aber Sie müssen es mit diesem Stereotyp, nennen Sie
das, Create verziehen.
Das sieht also dann so aus, wenn Sie zum Beispiel einen Kreis haben
und eine Funktion, die den Kreis erzeugt, erzeuge heißt die dann, muss
also nicht Kreis heißen, dann schreiben Sie davor dieses Create, wenn
man einen Konstruktor in UML angeben möchte.
Wenn Sie wissen, dass Sie nachher natürlich in Java implementieren
werden, dann machen Sie besser einen Namen für diesen, den richtigen
Namen für diese Erzeugefunktion für den Konstruktor, damit dann keine
Diskrepanz zwischen dem UML Modell und den Java Klassen entsteht.
Also in Java werden Konstrukte mit einer Sichtbarkeit versehen, haben
aber keine deklarierten Rückgabetyp, das mache ich in UML auch nicht,
aber in UML habe ich also beliebig.
Bevor wir weitermachen, war der falsch, in UML kann eine Assoziation
mehr als zwei Inden haben.
Ja, stimmt, natürlich, Quaternäre, Assoziationen, wir haben immer nur
die zweiseitigen gezeichnet, aber kurz, wenn Sie sich und Spieler
hatten wir, als dreiwertige.
Das folgende Modell ist gut oder sinnvoll?
Sieht doch gut aus, oder?
Firma, Person, Name, String, Angestellte, die brauche ich natürlich.
Wie ich die Assoziationen benenne, da bin ich eigentlich frei.
Ich habe es einfach mal umgedreht, aber selbst wenn ich es angestellt
beinennen würde mit dem Pfeil in der anderen Richtung, würde der
Fehler, der da drin steckt, immer noch dabei sein, den ich eigentlich
raus wollte.
Ja, Sie in den blauen.
Das hier ist redundant, denn diese Assoziation wird komplett
ausgedrückt durch diesen Pfeil oder diese möglichen Verknüpfungen und
dass ich die jetzt in einem Array speichere, das sage ich in OML nicht
mehr.
Das brauche ich nicht, denn es ist bereits durch Assoziationen
ausgedrückt und übrigens könnte es ja sein, dass ich gar kein Array
benutze, sondern eine Menge.
Und da gibt es auch wieder Möglichkeiten, ich könnte Hash-Sets
benutzen oder andere Dinge.
Ich könnte eine Liste benutzen, also in OML würde ich das auch gar
nicht festlegen wollen.
Okay, so, Vererbung.
Ja, das ist eine gute Frage, weil wir hatten nichts gesprochen über,
in welche Richtung ich traversieren kann.
Also wir denken uns das hier weg.
Jetzt steht die Assoziation da, ohne Pfeile.
Das heißt, wer das modelliert hat, hat uns im Unklaren gelassen, in
welche Richtung wir traversieren können.
Normalerweise heißt es eins und eins, Entschuldigung, heißt es
eigentlich in beiden Richtungen, aber es ist nicht garantiert.
Aber vielleicht ist es hier uninteressant.
Wenn ich möchte, wenn ich modellieren möchte, dass ich von der Person
zur Firma, bei der sie angestellt ist, traversieren kann, dann muss
ich hier einen Pfeil hin machen und wenn ich garantieren möchte, dass
ich von der Firma immer herausfinden kann, dass ich die angestellte
Person, muss ich hier noch einen Pfeil hin machen, also einen
doppelten Pfeil.
Dann kann ich in beiden, dann habe ich modelliert, ich will aber
garantieren, dass beide Richtungen navigierbar sind.
Wenn ich nichts sage, ist es mir als Modellierer egal und die
natürliche Implementierung wäre, hier eine Menge zu benutzen, sobald
es eine Menge gibt, kann ich zumindest in der Richtung traversieren,
aber nicht in der anderen.
Also ja, das mit der Traversierbarkeit, wie gesagt, ist so ein
bisschen unscharf hier in diesem Beispiel.
Ich könnte es zum Beispiel ganz explizit sagen, was ich will, indem
ich hier einen Pfeil hin mache, der sagt, ich kann von Firma zur
Person traversieren, aber nicht umgekehrt, dann würde ich hier ein
Kreuz hin machen.
Dann habe ich gesagt, was ich will, ins letzte Detail.
Aber wenn es für die Modellierung unerheblich ist, dann lasse ich es
offen, dann kann der Programmierer entscheiden, ob er mal irgendwo ein
Programm hat, wo er von Personen zurückschließen muss, auf die Firma,
dann soll er halt machen, was er braucht, indem man zusätzliche
Verknüpfung einfügt.
Oft ist dem egal, bei der UML-Modell, das einfach, kann man nicht
wissen, was da gebraucht wird und sagt man dann auch nicht.
So, gibt es noch Fragen, bevor ich weitergehe?
Da habe ich einfach angenommen, es gibt keine mehr.
Okay, gut, also jetzt, Vererbung, auf das haben Sie doch die ganze
Zeit gewartet, oder?
Das ist das Hauptinteressante, Teil von der ganzen objektorientierten
Geschichte.
Die stellen wir jetzt aber am Schluss.
Sie ist eigentlich theoretisch gesehen ganz was Einfaches.
Wir haben es eigentlich schon gesagt, aber ich führe jetzt erstmal die
Bezeichnungen ein.
Wir sagen, das hier wäre eine Oberklasse, komischerweise auch die
Basisklasse, obwohl sie ganz oben ist, ja, also Basis ist für mich
immer unten, oder auch Superklasse oder auch Generalisierung.
Zum Beispiel für ein Bandlaufwerk, da gibt es drei Methoden, Play,
Record, Magnetkopf, Kalibrieren oder sowas und davon gibt es jetzt
drei Unterklassen, die heißen Videorekorder, Daterekorder,
Kassettendeck, könnte man an jedem DVD-Rekorder, alles mögliche könnte
man noch hinzuschreiben und die Dinger nennt man Unterklasse oder
Subklasse oder Spezialisierung.
Ja, all diese Begriffe werden benutzt und ferner nennt man das hier
mit diesem großen Pfeil hier eine sogenannte Ist-Ein-Beziehung oder im
Englischen Isser, weil man sagt, ein Daterekorder ist ein Bandlaufwerk
und ein Kassettendeck ist auch ein Bandlaufwerk und ein Videorekorder
ist auch ein Bandlaufwerk.
Deswegen Ist-Ein oder Isser.
Man kann das auch aus der Superklassenbeziehung bezeichnen,
betrachten.
Diese Linien hier sehen aus wie Assoziationen, sind aber keine,
sondern diese Linien mit diesem großen Pfeil sagen etwas völlig
anderes als eine Assoziation, nämlich die geben mir eine Untermengen-
oder Kategorisierungsinformation.
Also und jetzt kommt die Vererbungsdefinition in aller Schönheit.
Es seien A und B Klassen, sowie Omega A und Omega B, die Mengen der
Objekte, die die Klassen A und B ausmachen.
Sie erinnern sich, wir sprachen von der Menge Omega, die kann ich in
Kategorien einteilen, in Untermengen und da sage ich mal gut, Omega A
und Omega B seien die Menge der Exemplare, die die Klassen A und B
ausmachen.
Klassen sind ja nur Mengen.
So und dann sage ich, B ist eine Unterklasse oder Spezialisierung von
A oder umgekehrt, A ist eine Oberklasse, Generalisierung von B oder
eine Basisklasse von B, wenn gilt, dass diese Objektmengenbeziehungen,
das nämlich die Unterklasse Omega B bestenfalls eine Untermenge der
Menge A ist.
Einfach eine Untermenge, weniger.
Kann auch gleich sein unter Umständen, dann habe ich eine schlechte
Modellierung, unnützige Superklassen gebildet, aber vielleicht will
man das manchmal haben für zukünftige Erweiterungen.
Man sagt dann auch, dass die Unterklasse B von seiner Oberklasse erbt.
Warum?
Weil es die gleichen Attribute haben soll, wie die Oberklasse.
Wir sagen ferner, dass das Exemplar von B, diese Unterklasse, selbst
wenn hier die strenge Untermengenbeziehung gilt, wir sagen dann, dass
B auch ein Exemplar von A ist.
Klar, es ist ja ein Element der Untermenge und deswegen heißt es auch,
ist ein Beziehung.
In der Regel sagt man dann, wenn ich bilde, ich natürlich mehrere
Unterklassen und was ich in der Regel haben will, ist, dass diese
Unterklassen alle disjunkt sind, denn sonst fällt es mir schwer zu
sagen, zu welcher Klasse gehört es.
Das kennen Sie auch in Java, wenn Sie eine Unterklasse haben von B und
C, dann wollen Sie natürlich, dass Sie disjunkt sind.
Es gibt in Java auch keine Mehrfachvererbung, das heißt, Sie können
nicht eine dritte Klasse hier sich vorstellen, von der ich auch noch
vererbe, weil dann es blödsinnige Regelungen geben muss, wenn ich ein
Attribut mit dem gleichen Namen von zwei unterschiedlichen Klassen
bekomme, dann weiß ich nicht, welche, ist es beide oder nur eine davon
und wenn nur eine davon, welche, wenn es dann auch nur in Typen
unterschieden sind, das wird dann unhandlich, darum vermeidet man das
in Programmiersprachen, die Mehrfachvererbung, aber selbst wenn man
keine Mehrfachvererbung hat, möchte man gerne, dass diese Unterklassen
sich nicht überlappen.
Ich sagte vorhin, die Natur spielt da manchmal nicht mit, gibt es ein
ganz bekanntes Beispiel, kann man hier nicht so richtig lesen, dieses
Viech hier ist ein sogenanntes Schnabeltier oder Platypus und das hat
die Eigenschaft, dass es eigentlich ein Säugetier ist, eigentlich ist
es ein Säugetier, aber es legt Eier, also eigentlich ist es ein Vogel.
Ist es jetzt das eine oder das andere?
Aber solche Dinge kommen selten vor, Gott sei Dank.
So und jetzt schauen wir uns mal ein paar schlechte Beispiele für
Vererbung an.
Ich sage mal, ich hätte eine Klasse 1 Int, die enthält genau ein
Integer und sage ich, okay, ich brauche jetzt eine Klasse 2 Int, da
vererbe ich von der 1 Int und mache dafür einfach das Y hinzu.
Ist das eine gute Modellierung?
Die Frage ist, ob diese Klasse, ob hier tatsächlich eine Ist
-Einbeziehung vorliegt, liegt hier eine vor?
Ist es eine 2-elementige Klasse?
Eine 1-elementige Klasse, das macht doch keinen Sinn, oder?
Da ist diese Modellierung verkehrt, das werden wir später noch klären.
Und zwar, Sie können jetzt folgendes sagen, ist die Menge der Objekte,
die 2 Int beschreibt, nicht deutlich größer als Omega 1 Int?
Das ist nämlich so, dass hier 32 Bit hier sind, hier sind 64 Bit, das
heißt ich habe jetzt 32 Elemente und 2 Int zu 64.
Das stimmt doch was nicht, ja?
Also jetzt wieder mal von der Namensgebung abgesehen.
Wie kann das Ding eine Unterklasse sein, wenn es mehr Möglichkeiten
hat?
Die Frage ist, hat die Unterklasse nicht immer mehr Attribute oder so?
Nicht unbedingt, also es gibt auch Unterklassen, die haben die gleiche
Anzahl von Attributen, genau wie sie gehabt haben, haben nur andere
Methoden.
Ich komme gleich, das ist tatsächlich ein Grund für große Verwirrung.
Und jetzt kommen wir zu Ihrem Punkt, den ich sagte, den stellen wir
zurück.
Die Menge 1 Int ist die Menge aller Objekte, die mindestens eine
ganzzahlige Variable mit Bezeichner X enthalten.
Das heißt also, die Menge 1 Int ist viel größer, als hier zu scheinen
es scheint.
Es ist nämlich nicht exakt eine, sondern diese Menge, diese Kategorie
gibt an, alle Objekte mit X Integer, mindestens.
Das heißt also, diese Menge hier beschreibt eigentlich alles, was
dieses X hat und alles dazu oder auch nur dieses eine.
Also davon müssen wir uns frei machen, dass das heißt exakt einer,
exakt das, was da steht und dann ist die Bezeichnung, dass das eine
Spezialisierung ist, auch eingängiger.
Eine Spezialisierung sagt, das eine ist eine ganz große Menge, das
sind die X und Y und Z und was weiß ich alles möglich drin, aber immer
mindestens ein X und das hier sagt, ich nehme alle, die haben ein X
und ein Y, mindestens.
Während diese Menge natürlich auch die Menge der Dinger, die haben X
und ein Z haben.
Da ist also tatsächlich seltsamerweise vom Konzept her eine
Untermengenbeziehung da.
Was man hier natürlich bedenken muss, wenn ich das Ding hier
instanziere, kriege ich natürlich nur dieses eine Element mit dem X
drin.
Wie sollte ich auch diese Riesenmenge, ein anderes Element auswählen
von unbekannten zukünftigen Attributen, die hinzugefügt werden können.
Bleibt uns nichts anderes übrig, aber konzeptionell sagt diese
Obermenge, diese Menge beschreibt alle Klassen, Entschuldigung, alle
Objekte, die mindestens das X als Integer haben.
Und dann ist natürlich selbstverständlich, dass hier eine und die
Anzahl der Elemente, Entschuldigung, die Anzahl der Instanzen, die
hiervon konzeptionell möglich ist, ist unendlich.
Das heißt, aber das ist definitiv eine kleinere Menge.
Also diese Beziehung besteht zwar, es ist keine schöne Modellierung,
weil Sie wahrscheinlich an Koordinaten denken oder so etwas, aber das
wollte ich erst mal gesagt haben, dass was Oberfläche, Obermenge
eigentlich bedeutet.
Alles, was mindestens das hier hat, dann ist das wirklich auch eine
echte Untermenge.
Okay, gut, jetzt noch mal mehr zu der Ist-Ein-Beziehung.
Jetzt schauen wir mal, statt Mengenbetrachtung betrachten wir mal das
sogenannte Substitutionsprinzip, zu dem wir gleich kommen.
Also angenommen, wir haben schon eine Klasse da, die hat also zwei
Koordinaten x und y, ist natürlich konzeptionell alle möglichen
Objekte, die mindestens das x und das y Float haben.
Und dann sagen wir, wir bräuchten auch eine Linie und Linie, da machen
wir uns das einfach, wir nehmen einfach den Punkt her und erweitern
den mit x2 und y2.
Ist erlaubt, ist völlig konsistent mit dem, was ich vorhin sagte, aber
es ist keine schöne Modellierung, denn konzeptionell ist eine Linie
kein Punkt.
Das sind zwei unterschiedliche Dinge, zwei unterschiedliche Klassen.
Da ist das eine faule Modellierung, da habe ich halt gesagt, ich will
weniger schreiben oder sowas, ja, aber eine saubere Modellierung wäre
vom Konzept her ist eine Linie kein Punkt.
Punkte ist ausdehnungslos, Linie ist das nicht.
Okay, da sehen Sie, da gibt es noch ein paar Dinge zu beachten.
In der Praxis habe ich auch noch einen anderen Grund, Obermengen oder
Oberklassen zu definieren, wenn ich finde, dass es mehrere Klassen
gibt, die unterschiedlich sind, die aber eine gemeinsame Teilmenge von
Attributen, Zuständen, Assoziationen und Methoden enthalten, dann kann
ich die gewissermaßen aus diesen Klassen herausziehen, in eine
gemeinsame Oberklasse setzen, die Methoden dafür hinzufügen, die ich
für diese Attribute brauche und dann Spezialisierungen machen, die
dann nur noch das Delta angeben.
Das spart mir natürlich Rechenarbeit oder Programmierarbeit und war
hier natürlich auch benutzt worden, aber aus dem falschen Grund.
Ich müsste immer dafür sorgen, dass das Substitutionsprinzip gilt, was
ich noch nicht gesagt habe, das kommt aber gleich.
Also in diesem Fall vermeide ich Redundanzen, ich muss nicht die
gleiche Methode dreimal schreiben zum Beispiel.
Okay, jetzt kommt dieses genannte Substitutionsprinzip, das eine Frau
Barbara Liskov vom MIT hat das als erste formuliert, deswegen auch das
Liskov Substitution Principle in einem Programm, in dem U eine
Unterklasse von K ist.
Ich habe also Unterklasse U, K-Klasse ist oben.
Kann jedes Exemplar der Klasse K, jedes Exemplar der Unterklasse,
Entschuldigung, ersetzt werden, durch ein Exemplar von U ersetzt
werden, wobei das Programm weiterhin korrekt funktioniert.
Also konkret gesprochen sagen wir Folgendes, wir haben eine Variable
vom Typ K, das ist die Klasse.
Ich speichere in dieser Variable einen Verweis auf ein
Unterklassenelement, tue aber weiterhin so, als hätte ich ein K
-Element und ich kann jetzt weiterhin Aufrufe ausführen, die
funktionieren, weil sie in der K-Klasse stehen, obwohl ich mit einer
Unterklasse arbeite.
Das müssen wir uns nochmal genau anschauen.
Alle Eigenschaften der Oberklasse müssen in der Unterklasse erfüllt
sein, wissen wir ja, notfalls vererben wir die.
Und die Unterklasse darf höchstens gleiche Vorbedingungen haben für
das Ausführen von Methoden oder Schwächere.
Das heißt, sie sind jederzeit zu erfüllen, wenn ich im
Oberklassenkonzept bin.
Also jetzt brauchen wir ein Beispiel.
Also angehend, wir haben die Klasse Person und eine Unterklasse davon,
die heißt Student.
Für die Klasse Person habe ich einen Satz von Methoden.
Ich sage Person kann ich anstellen, ich kann das Wahlrecht bestimmen,
ich kann die Adresse angeben, die Adresse abfragen und dann nehme ich
eine Unterklasse Student, der hat noch ein paar mehr, der hat eine
Matrikelnummer, das Semester in dem er ist, hat Noten, die er in
Klausuren erreicht hat, das ist die Unterklasse.
Jetzt nehme ich eine Instanz der der Klasse Student, S, und speichere
sie in eine Variable vom Typ Person.
Die Variable vom Typ Person sei P. Jetzt rufe ich eine Methode auf,
die sagt p.getAddress.
Das muss laufen, obwohl ich ein Unterklassenelement habe und deswegen
nennt man das das Substitutionsprinzip.
Ich habe für Person ein Unterklassenelement substituiert und rechne
weiter, als wäre es ein Oberklassenelement und das funktioniert,
solange die Unterklasse eben all das zur Verfügung stellt, was die
Oberklasse gehabt hat und das ist in der Regel, wenn ich nicht mich
dumm anstelle, auch der Fall, aber es gibt auch Ausnahmen.
Und die Unterklasse bietet mir dann, wenn ich zurückkomme, die
gleichen Nachbedingungen, gibt mir also nicht das falsche,
Informationen über das Wahlrecht oder stärkere, das Beispiel eignet
sich dafür jetzt nicht.
Also vererbte Eigenschaften, das sind Attribute, Assoziationen,
Zustände, Methoden, stehen der Unterklasse zur Verfügung, wie sie in
der Oberklasse definiert sind.
Die Unterklasse darf zusätzliche Eigenschaften anbieten, damit
verkleinert sich die Menge, erstaunlicherweise verkleinert sich die
Menge und die Unterklasse kann keine Eigenschaft der Oberklasse
weglassen, denn sonst kann ich sie im Oberklassenkontext nicht mehr
substituieren.
So, gut, das habe ich gesagt.
Beispiel, wir haben folgendes, wir haben ein Hund kann heulen, das ist
eine Methode, es gibt eine Unterklasse Wolf, der Wolf ist eine
Unterklasse von Hund, also kann ich hergehen und sagen, wenn ich ein
Wolf -Objekt habe, kann ich heulen dran aufrufen und beim Polarwolf
ebenfalls, weil er die Heulen-Funktion bekommt.
Sie wissen, dass ich heulen natürlich hier, wenn die ein bisschen
unterschiedlich heulen würden, kann ich die Methode hier neu
definieren und damit überschreiben.
Okay, also ich kann dem Polarwolf die Nachricht heulen schicken oder
die Methode heulen aufrufen, das ist ihnen klar.
Unterschiede, es gibt eine Signaturvererbung und eine
Implementierungsvererbung.
Signaturvererbung heißt, ich bekomme nur die Signatur vererbt, was
Signatur ist, wissen wir jetzt, aber nicht die Methode, den
Methodenrumpf und die Implementierungsvererbung sagt, ich bekomme
Signatur plus Implementierung.
Es ist klar, wenn ich eine Implementierungsvererbung habe, brauche ich
auch die Signaturvererbung.
Wo habe ich denn Signaturvererbung in Java?
Abstrakte Methoden, noch eine.
Interfaces, also in Java haben sie die Signaturvererbung in Form von
Schnittstellen, Interfaces, da bekommen sie Methodenrumpfe,
Entschuldigung, Signaturen, da ist gerade verkehrt herum gesagt und
abstrakte Methoden funktionieren auch so.
Okay, dann gibt es eine Aussage, die sagt, eigentliche
Signaturvererbung ist alles, was wir brauchen in der
objektorientierten Analyse, objektorientierten Entwurf, das ist OOD,
Object Oriented Design, Object Oriented Programming, Object Oriented
Analysis, ist eigentlich alles, was wir brauchen.
Allerdings ist die Implementierungsvererbung gar nicht notwendig.
Allerdings ist es ziemlich bequem manchmal, wenn wir eine
Implementierungsvererbung auch noch haben, dann spart es uns
Schreibarbeit, wenn die Methode exakt die gleiche ist.
Aber zur Modellierung braucht man die Implementierungsvererbung
eigentlich gar nicht und Java als auch C-Sharp geben uns beides.
So und jetzt wissen Sie, dass ich, wenn ich eine Methode eigentlich
vererbt bekomme, sie überschreiben kann mit einer anderen
Implementierung, die vielleicht zusätzliche Attribute in Betracht
zieht, ein bisschen länger ist, ein bisschen was zusätzlich
durchführt.
Das wissen Sie, ja?
Wird neu implementiert.
Das hat nichts übrigens mit dem Überladen zu tun.
Das Überladen sagt, ich habe mehrere Methoden mit dem gleichen Namen,
aber unterschiedlicher Signatur.
Also eine Methode mit drei Parametern, eine Methode mit dem gleichen
Namen, aber fünf Parametern, kann ich deren Aufrufe unterscheiden und
das nennt man Überladen und nicht Überschreiben.
Überschreiben heißt, wenn ich es vererbt bekomme, aber nicht damit
zufrieden bin und eine andere schreibe.
Jetzt haben wir wieder ein längeres Beispiel, Modellierung von
geometrischer Figur.
Wir sagen, wer hat eine Position Punkt, eine Funktion Zeichnen und
eine Funktion Lösche.
Es gibt Unterklassen Kreis, Oval und Rechteck.
Die haben dummerweise andere Attribute.
Der hat ein Radius, Position haben wir, das nehmen wir dann als
Zentrum.
Ein Oval hat zwei Radii, ein Rechteck hat Länge und Breite.
Achsenparell ist Rechteck, das ist ja.
Die Funktion Zeichnen wird überall überschrieben, denn ja klar, ein
Kreis zeichne ich anders als wie ein Rechteck.
Da muss ich andere Funktionen aufrufen und beim Oval genauso.
Eigentlich kann ich geometrische Figur hier oben gar nicht zeichnen,
alle möglichen geometrischen, die Funktion kann ich gar nicht
beschreiben.
Ich brauche hier in der Tat eine abstrakte Methode und die abstrakte
Methode schreibt man in UML kursiv.
Das kann man hier kaum sehen, auch in Klausuren und Hausaufgaben kann
ich das nicht sehen, ob sie mal kursiv schreiben und mal nicht.
Darum gibt es noch eine andere Methode dafür, die heißt, da muss man
das Wort abstrakt davor stellen, so wie Create mit den spitzen
Klammern.
Also diese Methode, diese Klasse ist dann übrigens auch abstrakt, ist
auch schon kursiv geschrieben,
aber ich bräuchte, es müsste es eigentlich so schreiben, abstrakt
zeichne, abstrakt geometrische Figur.
Ja, bitte?
Nee, das ist nicht der Fall, wenn Sie mal schauen, diese Klasse ist
abstrakt, es ist nur eine Methode abstrakt, es müssen nicht alle
abstrakt sein, nur wenn hier drüber stünde Interface, dann ja.
Abstrakt bedeutet, wir vererben an die Unterklassen nur die Signatur
und die Unterklasse hat eine Verpflichtung, diese Signatur zu
implementieren.
Es gibt noch mal eine abstrakte, eine zusätzliche Vererbungsstufe, wo
es dann implementiert wird.
Also das bringt mich auch gleich zu dieser Bemerkung, wenn ich also
eine abstrakte Methode in diesem Kreis hier nicht implementieren
würde, sondern noch mal einen doppelwandigen Kreis hätte oder sowas,
dann brauche ich sie hier nicht mehr aufzuführen.
Wenn ich aber möchte, dass sie hier implementiert wird, dann schreibe
ich sie hin.
Fragen dazu?
Schnittstelle.
Schnittstelle gibt es auch, kennen Sie natürlich aus Java.
Schnittstelle schreibt man so, das können Sie jetzt ganz schlecht
sehen, ich auch.
Spitzeklammer, Interface, zwei Spitzeklammern oder kleiner, kleiner
Interface, größer, größer.
Und dann sind automatisch alles das hier abstrakt.
Und anstelle eines Unterklassenpfeils schreibt man einen gestrichelten
Unterklassenpfeil, an den kann man auch noch Realize hinschreiben,
aber das Stricheln, das können Sie sicher schreiben, ist also nicht
zwingend, dass das Realize dasteht, aber das heißt, das hier muss
tatsächlich diese abstrakten Funktionen implementieren.
Ich muss sie hier oben nicht mehr als abstrakt markieren oder kursiv
schreiben.
Das Interface hier reicht und Sie sehen hier SMTP Server ist die
Schnittstelle mit Open, Send und Close als Methoden und der MySMTP
implementiert sie.
Der Programmierer kriegt die Aufgabe, in dieser Klasse diese
Funktionen zu implementieren.
Abhängigkeiten von einer Schnittstelle gibt man über einen Uses-Pfeil
an, der ist auch gestrichelt.
Die dritte Form des gestrichelten Pfeils, die kennen wir schon, das
ist der gestrichelte Pfeil von Instanz zu Klasse.
Was brauchen wir noch?
Da haben wir alles gesagt, ja Schnittstelle.
Also das hatte ich auch schon gesagt, Verpflichtung zu implementieren.
Umgekehrt gibt die Schnittstelle dann auch die Garantie ab, dass diese
Methoden vorhanden sind.
Und das Substitutionsprinzip gilt auch zwischen Schnittstelle und
Implementieren der Klasse.
Ich kann an jeder Stelle, wo eine Schnittstelle steht, also ich kann
ja eine Variable vom Typ SMTP Server angeben.
Nur ich kann keine Instanz davon bilden, also kein New SMTP Server
aufrufen, weil es das Ding abstrakt ist, ist klar.
Aber in diese Variable kann ich eine Instanz von MySMTP stecken.
Und das ist das Schöne an den Schnittstellen.
Schnittstellen geben mir die Methoden vor, die ich aufrufen darf.
Ich kann mein Programm weiterschreiben, als hätte ich schon ein
Objekt.
Und erst zur Laufzeit bekomme ich wahrscheinlich eine echte Instanz,
die eine Unterklasse ist, von einer Unterklasse ist, in die
Schnittstellenvariable gespeichert und kann weiterrechnen damit.
Als hätte ich von vornherein hier eine Instanz gehabt.
Okay, dazu habe ich noch ein bisschen was zu sagen.
Also ist auch hier ein Substitutionsprinzip da.
Okay, na gut, das brauche ich Ihnen jetzt nicht zu sagen.
Ich wollte jetzt auf etwas anderes eingehen.
Wie viel Zeit haben wir noch?
Wir verwenden also im Substitutionsprinzip, muss ich nochmal
zurückgehen, so verwenden, als wäre es ein Exemplar der Oberklasse.
Es wird nicht gefordert, dass die Signatur gleich bleibt.
Moment, das hatten wir jetzt aber noch nicht gesehen.
Ich kann die Signatur bei Vererbung ändern, solange das
Substitutionsprinzip erhalten bleibt.
Jetzt denken Sie natürlich an zusätzliche Parameter, aber das meine
ich nicht.
Es gibt andere Arten, die ich verändern kann, nämlich ich kann die
Typen der Parameter ändern und das ist jetzt ein bisschen kompliziert.
Also bei diesen Parametern gibt es die, die in der Unterklasse und der
Oberklasse unterschiedlich sind, gibt es die folgenden Begriffe
Varianz, Kovarianz, Kontervarianz und Invarianz.
Varianz heißt eigentlich Modifikation der Typen von Parametern in
überschriebenen Methoden.
Kovarianz sagt, ich verwende eine Spezialisierung des Parametertyps in
der überschreibenden Methode.
Also ich habe Methode irgendwas mit Parameter Person.
In der Unterklasse habe ich die gleiche Methode mit den gleichen
Namen, aber ich nehme nicht Person, sondern Student dafür.
Die Frage ist, ist das erlaubt?
Kontervarianz sagt, es geht gerade umgekehrt.
Ich verwende eine Allgemeinerung und Invarianz sagt, es ist exakt
gleich.
Wenn ich in der Oberklasse Person sage, muss ich in der Unterklasse
auch Person als Parameter angeben.
Dazu brauchen wir jetzt ein Beispiel, weil das komplizierter ist.
Also Oberklasse R, Unterklasse S.
Oberklasse R hat eine Funktion, die heißt N, egal wie sie heißt und
hat einen Parameter vom Typ B.
Unterklasse hat N, hat auch einen Parameter, aber der ist vom Typ A.
Und wir haben die folgenden Bewerbungsbeziehungen.
S ist eine Unterklasse und die Beziehung hierum ist gerade
andersherum.
Sie sehen also, ich habe hier eine Oberklasse A und B, hier steht die
Oberklasse, hier steht die Unterklasse.
Deswegen Kontervarianz.
Damit kann man sich das merken.
Die Beziehung zwischen Klassen geht in die eine Richtung und die
zwischen den Parametern in die andere Richtung.
Und da fragen Sie natürlich, kann ich damit überhaupt was machen?
Das sehen wir gleich.
Da kann man in der Tat was damit machen.
Die andere Methode ist, das ist die Kovarianz, die sagt, ich bin,
Bewerbungsbeziehungen zwischen den Klassen und den Parametern der
Methoden in diesen Klassen ist in der gleichen Richtung.
Kovarianz, kovarieren.
Nützt mir das was, werden Sie fragen.
Das geht in der Tat.
Also wir machen jetzt folgendes Beispiel.
Wir machen jetzt einen Versuch, der sagt, unter welchen Umständen
können wir Kontravarianz benutzen und das Substitutionsprinzip
erhalten.
Also wir haben so eine Situation wie vorhin.
Einmal die Beziehung in der Richtung und einmal in der anderen.
Also Europäer, Kroate.
Europäer nimmt den Euro entgegen, der Kroate nimmt alles.
Währungen.
Entschuldigen, ich muss mich bei Kroaten entschuldigen, aber wir
nehmen jetzt mal diese Modellierung her.
Wir wissen natürlich, dass Euro eine Unterklasse von Währung ist.
Das ist eine Währung.
Euro ist eine Währung.
Wunderbar.
So und jetzt ist die Frage.
Wir haben ein Europäer E, wir haben einen Kroaten K, wir haben eine
Währung W und einen Euro E.
Das sind unsere Variablen, initialisiert, egal, das schreibe ich jetzt
nicht mehr hin.
Ich kann jetzt natürlich hernehmen E und die Nehmen-Funktion aufrufen
und ein Euro geben.
Ist das erlaubt?
Ja, es ist erlaubt, denn das haben wir ja.
Wir haben ein Europäer, der nimmt ein Euro.
Also gut, das ist offensichtlich erlaubt.
Jetzt mache ich folgendes.
Ich speichere in dieser Variablen E einen Kroateninstanz ab und mache
die gleichen Aufrufe.
Den gleichen Aufruf nehmen, Euro.
Ist das erlaubt?
So, warum geht das?
Naja, ich rufe an diesem Objekt, also der Compiler würde dazu
überhaupt nichts sagen, der würde sagen, das ist voll okay.
So, jetzt muss man nachdenken, was hier eigentlich in diesem Aufruf
passiert.
In diesem Aufruf passiert folgendes.
Nachdem ein Kroate hier drin steckt in dem E, welche Methode wird denn
eigentlich aufgerufen?
Was eigentlich aufgerufen wird, ist natürlich diese hier.
Das heißt, der Kroate wird nur Dinge machen, die er mit Währungen
allgemein machen kann, nämlich tauschen.
Aber was haben wir ihm gegeben?
Wir haben ihm hier einen Euro gegeben und das ist eine Währung.
Er arbeitet nur mit der Oberklasse-Währung, nimmt nur Methoden, die
auf Oberklasse zugeht.
Keine Methode, die auf Euro spezialisiert ist.
Ich weiß jetzt keine, aber wir können uns irgendwelche ausdenken.
Auf Euro spezialisierte Methoden wird er nicht benutzen, also läuft
das durch.
Das läuft in der Tat.
Das ist in der Tat erlaubt.
So, so weit, so gut.
Jetzt nehmen wir den anderen Fall.
Die Co-Varianz.
Ein anderes Beispiel.
Jetzt muss ich mich bei noch mehr Leuten entschuldigen, aber
angenommen, wir haben einen Baden-Württemberger, die essen nur Nudeln.
Die essen nur Nudeln, fertig.
Die Schwaben sagen, wir essen von den Nudeln nur die Spätzle.
Das ist jetzt die Co-Varianz.
Sehen Sie, die Nudel ist der Oberbegriff, Spätzle ist der
Unterbegriff.
Die Beziehungen hier gehen beide in die gleiche Richtung, ist also Co
-Varianz.
Jetzt machen wir das gleiche Spielchen.
Gleiches Programm, andere Namen, andere Typen.
Baden-Württemberger, BW, Schwabe S, Nudel N, Spätzle SP.
Klar, müssen wir noch initialisieren, sonst geht gar nichts und
kriegen lauter Fehlermeldungen, aber das sei so.
BW.Essen, das ist der Baden-Württemberger, Oberklasse, Baden
-Württemberg ist die Oberklasse.
BW.Essen, Essenfunktion gibt's.
Was geben wir ihm?
Eine Nudel, sagt er wunderbar, das esse ich.
Jetzt machen wir BW ist gleich S, speichern wir BW in den S rein.
Dann machen wir den gleichen Aufruf.
Der Compiler wird sagen, alles in Ordnung.
Was passiert zur Laufzeit?
Ich habe hier eigentlich einen Schwaben, dessen Methode wird
aufgerufen.
Jetzt nochmal zurückblättern.
Dessen Essen, die Schwaben, nur diese Methode wird aufgerufen, denn
das ist das Subjekt, das ich habe.
Das heißt also, ich rufe das Schwaben-Essen-Methode auf und der sagt,
ich betrachte es als eine Spätzle und sagt dann, gut für Spätzle habe
ich eine spezielle Methode, die esse ich nur mit Löffeln zum Beispiel.
Und schon fliegen wir raus.
Jetzt kriegen wir einen Laufzeitfehler.
Das geht nicht.
Man muss immer bedenken, wenn ich in die Oberklasse etwas von der
Unterklassenelemente abspeichere, ist erlaubt, ich kann den Aufruf
auch tun, den es in der Oberklasse gibt, aber was aufgerufen wird, ist
das, was der aktuelle Objekt enthält, nämlich die Essen-Methode der
Unterklasse und die muss geeignet sein, mit dem Objekt fertig zu
werden, das ich bekomme.
Also hier bekommen Sie einen Laufzeitfehler.
Okay, wir sind fast fertig.
Eine Minute brauche ich noch.
Also, um das Substitutionsprinzip zu erfüllen, sind folgende
Modifikationen der Parametertypen zulässig.
Eingabe-Parameter, da nehme ich Kontravarianz.
Ausgabe-Parameter, einschließlich Rückgabewert, ist es gerade
andersrum.
Das dürfen Sie aber selbst durchdenken, das geht so wie jetzt.
Nehmen Sie sich bitte noch ein paar Minuten Ruhe.
Ich höre mich selber nicht mehr.
Also, Sie nehmen sich eine Methode her, geben Ihren Rückgabetyp und
versuchen den Unterschied mit dem Rückgabetyp mit Kovarianz und
Kontravarianz und dann werden Sie sehen, dass Sie für den Parameter,
der zurückkommt, also der Rückgabetyp, der Rückgabewert, da dürfen Sie
nicht Kontravarianz nehmen, sondern Kovarianz.
Wenn Sie einen Parameter haben, das ist in Java nicht möglich, aber in
C -Sharp zum Beispiel, der sowohl ein- als auch Ausgabe-Parameter ist,
dann bleibt Ihnen nichts anderes übrig, als Invarianz zu nehmen.
In diesem Programm ist nicht all dieses zulässig, aber in UML ist es
zulässig, weil es das Substitutionsprinzip erfüllt.
Also diese Einschränkungen sind gegeben.
Hier hören wir auf.
Vielen Dank.