Ja, hallo. Herzlich willkommen zur dritten Vorlesung von OOOPM
. Mein Name ist Axel Schmolitzki, die meisten von euch
werden mich kennen aus SE1. Ich habe jetzt einen Lehrauftrag,
bin momentan beschäftigt an der Hochschule für angewandte Wissenschaften.
Hier in Hamburg am Berliner Tor und habe aber jetzt noch zusätzlich
einen Lehrauftrag und werde einige Vorlesungen aus SE2 noch halten.
Die heutige gehört dazu. Heute geht es um Mein Lieblingsthema,
nämlich um Vererbung und Vererbungskonzepte. Und ja, schau mal,
inwieweit das noch alles hier funktioniert mit der Technik.
Ich bin ganz zuversichtlich, bis auf Storesi, Storesi,
zickt so ein bisschen, aber ich glaube,
die letzten beiden Male habt ihr Storesi auch gar nicht eingesetzt.
Ich bin nicht der Einzige, der die Technik nicht zum
Laufen kriegt. Da gab es jetzt gerade ein Update,
das hat auch nicht alles so gut funktioniert.
Also es ist ja keine professionell entwickelte Software,
sondern ein Prototyp. Da muss man sich darauf einlassen,
dass es auch manchmal ein bisschen schief ist.
Die letzten beiden Vorlesungen waren so ein bisschen Einstieg zum einen und
dann letzte Woche ging es um Testen und um das Vertragsmodell.
Das war so ein erster Einstieg. Ich werde heute auch auf
das Vertragsmodell eingehen. Und zwar im Zusammenhang mit Vererbung.
Das ist nämlich so ein Zusammenspiel, was relativ spannend ist.
Und ich werde aber jetzt erst mal einen Überblick geben über
das ganze Thema Vererbung. Wird es dann so zergliedern,
dass wir heute so mit dem einen Kernkonzept beschäftigen,
nämlich mit Polymorphie und Typhierarchien
Und in der nächsten Woche dann mit Implementationsvererbung.
Das heißt also, wir trennen das ziemlich sauber hier.
Das sind unterschiedliche Konzepte. Und ihr solltet die auch zumindest
mal unterschiedlich behandelt sehen, also sehr stark differenziert sehen,
auch wenn die in der Praxis häufig sehr eng beieinander liegen.
Okay, das heißt also, ja, ich habe schon wieder gefreest.
Ja, sehen wir wieder toll. Also das war das,
was ich eben so, das war nicht viel.
Ich glaube, es gibt auch kein Skript momentan, oder?
Im Kommen Sie ist nichts zur Verfügung.
Gut, gut. Dann sind das jetzt sozusagen frische Folien
für euch
Total aufregend. Also Polymar 4 heute,
Vererbungskonzepte und dann vor allen Dingen Typ Hierarchien.
So, die ganze Motivation ist Vererbung als Thema.
Vererbung ist für viele die definierende Eigenschaft von objektorientierten Programmiersprachen.
Es gibt so ein PayPal von Peter Wagner,
der das schon irgendwie 1997 so definiert hat,
also eine objektorientierte Sprache ist, eine objektbasierte Sprache mit Vererbung.
So, also würde man ja meinen,
ihr müsstet ziemlich genau Bescheid wissen, was Vererbung ist,
damit ihr auch vernünftig objektorientiert programmieren könnt.
Das ist nun schon ein paar Jahre her, dass er
das gesagt hat. Heutzutage ist man um einiges schlauer,
das werde ich heute so ein bisschen verständlich machen,
warum das so ist
Das heißt, natürlich ist es richtig ohne Verständnis von Vererbung,
habt ihr keinen vollständiges Verständnis für EuroP, das ist schon notwendig,
aber ihr habt ziemlich viel objektorientiert programmieren können, ohne Vererbung.
Das ging ganz prima. Wir haben diese ganze Veranstaltung,
SE1 und SE2 eben halt genauso konzipiert, weil dieser Begriff Vererbung
so wahnsinnig überladen ist. Als ich irgendwann Mitte der 80er,
ne, Mitte der 90er, sorry,
angefangen habe über das Thema zu promovieren, da war so,
dann nach dem Motto, alle reden über Vererbung,
aber keiner weiß so genau,
was es eigentlich ist oder jeder versteht was anderes drunter
Wir wollen uns also angucken, was für Erbung eigentlich ist.
Und ein Grund, warum Vererbung auch so verschrien ist,
ist, weil man sich damit so wunderbar in den Fuß schießen kann.
Also man kann mit Vererbung ziemlich schlechte Software schreiben.
Das ist ganz großartig. Das ist so ein bisschen wie mit
dem Go-To. Ich hatte ja eine SE2, SE1,
was erzählt über das Go-To-Statement, das in höheren Programmiersprachen mal existierte,
in C zum Beispiel immer noch existiert oder CPlus.
In Java existiert es nicht. Ja,
und der gute Herr Darkstrat, der Mitte der 60er,
diesen Leserbrief geschrieben, wo er gesagt hat,
raus mit dem Go-To, das brauchen wir nicht,
das macht uns nur Ärger, gibt nur Spaghetti-Code und so,
ist eine Einladung dazu,
schlechten Code zu schreiben
Und so ein bisschen so ist es mit Vererbung auch.
Also wenn man Vererbung so undiszipliniert verwendet, nicht gut ausgebildet ist,
dann kann man mit Vererbung wildeste Sachen machen.
Und tatsächlich kann man sich mit Vererbung sehr viel mehr ins Knie schießen als mit dem Go-To. Das Go-To ist,
sagen wir, relativ stark eingegrenzt worden,
innerhalb einer Methode, innerhalb einer Prozedur,
wenn man da irgendwie hin und her springen kann,
dann ist der Code nicht mehr ganz so lesbar,
aber das ist dann ja relativ schnell begrenzt worden,
dass so ein Go-To nicht mehr einfach beliebig durch den gesamten Code springen kann,
sondern dass man schon innerhalb des Sichtbarkeitsbereichs bleibt.
Das ist bei Vererbung leider nicht so mit Vererbung,
kann man munter durch die Gegend erben und ziemlich viel Mist machen.
Ja, und also gerade wenn man große Systeme unsystematisch mit Vererbung irgendwie miteinander verknüpft,
dann kann man das sehr, sehr unangenehm machen.
Okay. Das heißt, was ich versuche,
jetzt rauszuarbeiten, ist, dass es Konzepte gibt,
also Programmierkonzepte, wollen wir Sprachkonzepte, die unterstützenswert sind,
die sehr nützlich sind für uns, für euch,
dass ihr sie kennt.
Und wir grenzen das ab von den Mechanismen, die es in Programmiersprachen dafür gibt.
Also so ein Mechanismus wie Interface in Java,
das ist ein Mechanismus, da gibt es also irgendwas,
eine Regel in der Sprache,
dann rumpelt irgendwas,
wenn man da was hinschreibt
Und was man damit unterstützt mit dem Konzept von,
mit dem Mechanismus-Interface ist das Konzept von Schnittstellen.
Ein Beispiel, was ihr auch schon kennengelernt habt.
Das ist tatsächlich das, was ihr schon kennengelernt habt,
nämlich hier Abstraktionsmittel-Interface. Ich habe in SE1 Interfaces
sehr untypisch eingeführt in den meisten Lehrbüchern werden Interfaces.
Anders eingeführt, habe ich schon ein paar Mal erzählt.
Bei uns war es so, oder für euch war es so,
ein Interface ist eine Möglichkeit, die Schnittstelle einer Klasse explizit zu beschreiben.
Es gibt also einen Mechanismus,
um Schnittstellen zu beschreiben
Und das Beispiel, was ihr schon gesehen habt,
unter anderem war Datentyp List als Interface und dann zum Beispiel
Link List und Areal List als zwei verschiedene Implementationen desselben Datentyps.
Ich habe da jetzt gruselt euch bitte nicht zu sehr.
Ich habe BlueJ dabei. Also ich benutze in der Vorlesung
immer noch mal ganz gerne BlueJ,
auch wenn ihr in den Übungen jetzt mit Eclipse arbeitet.
Aber BlueJ gefällt mir einfach nach wie vor sehr gut,
um Dinge in der Vorlesung zu visualisieren.
Hier habe ich ein Projekt, einen Ausschnitt aus einem Projekt,
was ihr kennt,
SE1 Tunes
Da war es so, dass wir ein Interface hatten,
Titelliste, ihr erinnert euch da hoffentlich dran. Ja,
da war so eine Logik von, es ist eine Liste von Titeln,
wo man die Reihenfolge manipulieren kann, wo auch Titel doppelt drin vorkommen können.
Und der Elementtyp in dieser Liste waren Titel,
das ist ja auch dabei, dann gab es eine Titelbibliothek,
die irgendwie so auch Titel erzeugen konnte,
irgendwie durchaus irgendwie einlesen aus der Textdatei.
Und wir haben hier dann zwei Implementationen gehabt, also zwei implementierende Klassen.
Und was ich jetzt hier dazu gepackt habe, ist SE1-Tunes,
sowas wie iTunes oder irgendwie so eine Software,
die Titellisten gerne ein bisschen hübscher darstellen, ein bisschen visualisiert.
Also das ist quasi hier stellvertretend für eine Software,
die solche Listen, solche Titellisten verwendet.
Ich habe ja auch, glaube ich, letzten Semester gezeigt,
dass man in iTunes ja tatsächlich Titellisten so verwenden kann,
wie genau unsere Listen.
So, wenn man sich den Klienten hier mal anguckt,
dann ist das relativ einfach. Hier,
die Klasse hat irgendwie eine private Exemplarvariable,
eine Titelbibliothek und legt sich hier eine an mit einem Jazz-Mix,
also sind dann irgendwelche Jazz-Titel in dieser Titelbibliothek drin und
da gibt es nur eine einzige Methode,
die einfach mal exemplarisch so
eine Titelliste benutzt
Das ist relativ einfach hier. Füge, zehn Zufallstitel an.
Also man übergibt hier als Parameter eine Titelliste und dann ist hier eine Vorschleife,
die dann irgendwie zehn Titel aus der Bibliothek rausholt und die in diese Liste einfügt.
Ja. Und das ist ziemlich einfach. Kann ich also
jetzt hier ein bisschen Exemplar erzeugen und dann diese Methode aufrufen.
Da müsste ich jetzt als aktuellen Parameter an dieser Stelle hier
eine Titelliste übergeben. Habe keine. Also was mache ich?
Ich erzeuge mir eine. Ihr seht, dass diese Klasse
hier nur das Interface-Titelliste verwendet. Diese Klasse weiß nichts davon,
dass es hier
verschiedene Implementationen gibt
Und ich kann aber jetzt dem Klienten hier unterschummeln, eine Implementation dieses Interfaces.
Ich kann also hier jetzt ein Exemplar erzeugen, der Array-Titel-Liste.
Ich kann mir auch hier einen Titel, ein Exemplar erzeugen, der Linktitel-Liste.
Ich habe jetzt zwei verschiedene Exemplare von zwei konkreten Klassen,
die beide das Interface implementieren. Und ich kann jetzt
sowohl einmal hier das Ding aufrufen mit diesem Ding hier,
das muss ich mir ganz kurz gucken,
ob ich hier auch die Konsole anzeigen,
mal eben ganz kurz, Option,
Konsole löschen beim Methodenaufruf.
So,
da kommt nämlich gleich die Konsole hoch
Glaube ich zumindest. Wenn ich jetzt hier sage, Flüge
zehn Zufallstitel ein. Und übergebe jetzt als Referenz hier,
das ist ja eine Referenz, die ich übergebe,
eine Referenz auf ein Objekt einer Klasse,
das dieses Interface implementiert, die das Interface implementiert,
dann funktioniert das.
Dann kann der jetzt hier tatsächlich zehn Titel einfügen.
Das heißt, jetzt ist in dieser Liste sind zehn Titel drin.
Ich kann also hier tatsächlich auch mir Schreiber auf Konsole ausgeben lassen.
Und wenn dann die Konsole hier irgendwo erscheint, tut sie nicht.
Wo ist die Konsole? Da, da ist die Konsole
. Es sind also tatsächlich zehn Titel da irgendwie reingeschmissen worden
Und bei dieser anderen Liste, da habe ich jetzt,
wenn ich schreibe, auf Konsole mache, ist da nichts drin.
Also hier Konsole, da tut sich nichts. Die ist
jetzt bei dem Aufruf gelöscht worden, deswegen ist da jetzt nichts zu sehen.
Und wenn ich jetzt auch hier wieder füge,
zehn Zufallstitel ein und nehme ich jetzt diese Liste, übergebe Dieselliste als Implementation,
kann ich da auch zehn Titel einfügen lassen, kann jetzt sagen,
schreibt die mal auf Konsole und dann kommen hier irgendwelche zehn anderen Zufallstitel raus.
Also, was ich eigentlich sagen wollte, ist,
ich habe ein Interface, eine Schnittstelle, ein Klient,
braucht nur diese Schnittstelle, ihm ist egal,
wie die implementiert ist und verschiedene Klassen hier können das Interface implementieren.
Und ich kann Exemplare an der Stelle verwenden.
Das ist etwas, was ihr schon kennengelernt habt
Und was da ein ganz zentraler Begriff ist,
ist hier der Begriff Polymorphie. Ich kann also tatsächlich da
etwas Polymorph benutzen und was dieser Begriff Polymorphie bedeutet,
das ist heute so ein bisschen zentraleres Thema.
Ich mache nochmal eine ganz kurze Schnelldurchlauf durch so ein paar Begriffe,
die ihr hoffentlich noch ein bisschen im Kopf habt, aus SE1.
Zum einen ganz wichtig die Doppelrolle einer Klasse. Ich habe in SE1 erzählt,
dass man zwei Sichten haben kann auf eine Klasse,
zum einen als Klient, dann will ich das Ding nur benutzen.
Das heißt, die Exemplare der Klasse möchte ich benutzen,
dann geht es mir darum, Gibt es denn da so,
welchen Typ haben die Parameter?
Was sagt die Dokumentation aus über die Benutzbarkeit dieser Klasse oder diese Exemplare der Klasse?
Das ist die Außensee, das sind die öffentlichen Eigenschaften,
das sind die Dienstleistungen, die das Ding anbietet, der Dienstleister.
Und das ist ganz wichtig, der Begriff des Typs.
Und andererseits, und das seid ihr genauso,
ihr seid ja ständig in der Objektorientierung, Klienten und Dienstleister.
Also ihr müsst sozusagen so beide Sichten haben,
wenn wir jetzt eine Klasse sehen, als sie ist für einen Klienten benutzbar.
Wir können aber auch die Klasse draufgucken,
in den Quelltext reinsehen und dann sind wir plötzlich Wartungsprogrammierer.
Wir müssen an dem Quelltext
was arbeiten
Da müssen wir mal ganz andere Sachen angucken.
Natürlich auch, was sollen sie denn tun?
Also was ist ihre Leistung nach außen? Aber wie ist
sie auch intern aufgebaut? Welche Methoden gibt es da?
Wie ist das in den Methodenrümpfen so umgesetzt,
was die Leistungen da, die nach außen versprochen werden?
Welche Exemplarvarianten gibt es da? Was für einen Zustand
haben die? Also innen sich private Eigenschaften und Implementationen.
Und das kann man ganz systematisch einfach immer unterscheiden.
Und bei einer Klasse ist es eben halt tatsächlich so,
dass eine Klasse immer beides ist.
Eine Klasse definiert einen Typ und sie ist aber gleichzeitig
auch eine Implementation,
dieses Typs
Und ein Interface ist tatsächlich nicht weiter als nur eine Typinformation.
Ein Interface definiert einen Typ. Ich kann also Variablen deklarieren
von dem Typ eines Interfaces und habe dann die Operationen,
die ich da aufrufen kann.
Hier haben wir tatsächlich ja auch dann begrifflich unterschieden,
schon in SE1 zwischen Methoden und Operationen.
Also wenn ich hier in diesem Beispiel jetzt mir den Klienten angucke,
nicht den Klienten, sondern den Klienten Quelltext,
dann ist es so,
hier der formale Parametalist ist vom statischen Typ Titelliste
Und der statische Typ Titelliste ist ein Interface und der definiert,
es gibt die Operation, füge ein.
Das ist hier erkennbar in dem Interface, steht das so drin.
Hier gibt es ein Füge ein. Steht da so drin.
Das ist eine Operation. In einem Interface stehen immer nur Operationen.
Nichts anderes. Weil in einem Interface dürfen ja keine Methodenrümpfe stehen.
Wir haben unterschieden, eine Methode enthält wirklich Code. Das heißt,
hier habe ich eine Operation, die heißt Füge ein.
Ich habe aber hier in den beiden Implementationen zwei Füge ein,
Methoden. Hier habe ich eine Methode,
die heißt Füge ein und hier habe ich auch eine Methode,
heißt Füge ein.
Beide Methoden
Dieselbe, die eine Operation. Und je nachdem,
was sich da dynamisch als Objekt reingebe,
wird die eine oder die andere Methode aufgerufen.
Deswegen die Unterscheidung hier zwischen Operationen und Methoden, relativ wichtig.
Und wenn man jetzt das ein bisschen skaliert nach außen,
also sagt, naja, so ein objektorientiertes System in Java,
wie sieht denn das aus?
Da sagen viele, ja, so ein Objektnatursystem besteht aus
ganz vielen Klassen in der statischen Sicht.
Stimmt, sind meistens ziemlich viele Klassen dabei,
aber es gibt eben halt in einem vernünftigen System auch ziemlich viele Interfaces.
Und wenn man das den Oberbegriff für beide bildet,
dann ist das eben halt
der Typ
Klasse ist ein Typ, ein Interface ist ein Typ, das sind die Typen.
Also ein System besteht eigentlich aus einer Menge von Typen.
Und die benutzen sich gegenseitig ausschließlich über ihre Schnittschiffe.
Deswegen ist in diesem Diagramm hier,
ist immer nur der Pfeil, der benutzt ein Pfeil,
geht immer nur auf diesen blauen Anteil, auf diesen Interface-Anteil.
Und die Implementationen sind irgendwie versteckt und tun dann etwas.
Bei den Klassen hängen da Intlementationen dran, bei den Interfaces eher nicht.
Gut. Dann ist relativ schnell notwendig gewesen in SE1,
dass wir diese Unterscheidung einführen zwischen statischem und dynamischem Typ
Ja, also ich muss bei einer Referenzvariablen genau das unterscheiden.
Das heißt, hier in dem Klienten ist es so,
der statische Typ von Liste ist Titelliste.
Also diese Variable ist eine Referenzvariable und die hat einen statischen
und einen dynamischen. Der statische Typ ist deklariert,
kann man im Quelltext ablesen und man weiß auch genau,
welche Operationen man das deswegen dann aufrufen kann.
Ich kann an dieser Stelle jetzt hier nicht aufrufen, Liste umbenennen,
liste.umbenennen. Umbenennen ist nicht Teil der Typdefinition von Titelliste.
Da wird der Compiler mir auf die Finger klopfen und statisch
zur Übersetzungszeit schon sagen, die Operation gibt es nicht
So, das ist gut, schön,
dass wir so eine Sprache wie Java haben,
wo uns relativ viel Feedback gegeben wird zu den statischen Eigenschaften.
Und dann ist es aber auch so, es gibt den dynamischen Typ einer Referenzvariablen.
Und das ist der, der eben halt dann zur Laufzeit
da drin steckt. Also das ist tatsächlich so,
wenn ich jetzt hier diese Methode aufrufe und entweder eine Linked-Titelliste
übergebe als Exemplar oder eine Array-Titelliste, dann ist je nachdem,
was ich übergebe,
der dynamische Typ dieser Variablen hier,
dem halt der eine oder der andere Typ
Das sollte euch nichts Neues sein. Das ist alles schon
dran gewesen. Ich wiederhole es einfach noch mal ganz schnell
im Schnelldurchlauf. Und ein Objekt ändert seinen Typ nicht,
aber eine Referenzvariable, die ja Referenzen auf Objekte hält,
kann durchaus ihren dynamischen Typ verändern.
Man kann da was Neues zuweisen und ähnliches. So,
und was ganz wichtig ist, ist,
dass der dynamische Typ einer Variablen darüber entscheidet, welche konkrete Methode aufgerufen wird.
Also, ich habe hier diesen Aufruf und das ist wirklich ein Operationsaufruf.
Hier steht liste.füge ein, also steht da ein Operationsaufruf.
Das ist das, was der Compiler daraus erzeugt
Und zur Laufzeit, wenn jetzt eine der beiden Referenzen,
die ich da auf der Objektleiste liegen habe, übergeben wird,
wird halt dadurch entschieden, was ich da übergebe,
welche konkrete Methode füge ein, wirklich aufgerufen wird.
Also welche konkrete Methode füge ein, wird wirklich ausgeführt.
Und die Entscheidung wird erst zur Laufzeit getroffen. Und wenn
etwas erst zur Laufzeit passiert, dann nennen wir das dynamisch.
Und im Jargon der Objektorientierung ist es einfach so,
dieser Methodenaufruf heißt dann, er wird dynamisch gebunden.
Das heißt, erst zur Laufzeit wird festgelegt, welche Methode aufgerufen wird.
So, das ist ein ganz zentraler Begriff,
dynamisches Binden
Erst zur Laufzeit steht der dynamische Typ fest,
also kann auch der Compiler dann erst,
kann er nicht vorher festlegen, welche Methode aufgerufen wird.
Das wird also tatsächlich dynamisch entschieden. Das wird in Java
im Allgemeinen für fast alle Aufrufe dynamisch entschieden.
Es gibt eine Ausnahme, die habe ich hier in
der Ecke so ein bisschen klein dazu geschrieben.
Private Methoden, also private Exemplarmethoden, die werden wirklich statisch gebunden.
Statische Methoden werden sowieso statisch gebunden, die Static heißen,
das ist klar. Aber wenn ihr an einem Exemplar
eine private Methode habt, dann wird die auch tatsächlich
statisch gebunden
Da kann der Compiler schon zur Übersetzungszeit genau sagen,
da wird der und der Code aufgerufen. Das ist aber
nur ein Detail. Alle anderen Aufrufe werden dynamisch gebunden.
Das ist einfach in Java, eine Sprachentscheidung gewesen,
die die Sprache sehr, sehr mächtig macht, sehr flexibel macht.
Und ist auch gut so. Okay, jetzt habe ich
sehr ausführlich nochmal wieder dargelegt, warum ich es wichtig finde,
zu unterscheiden zwischen dem Typ einer Klasse und der Implementation einer Klasse.
Also eine Klasse ist irgendwie beides. So,
und wenn wir diese Unterscheidung akzeptieren, dann kann ich jetzt einsteigen,
zu versuchen zu erläutern,
was Vererbung ist
Und meine allgemeinste Aussage, die ich glaube,
die man irgendwie in jedem Kontext sagen kann,
ohne dass man irgendjemanden auf die Füße tritt, ist,
mit Vererbung werden hierarchische Beziehungen modelliert.
Das trifft es eigentlich ganz gut.
Also irgendwie geht es um Hierarchien, hierarchische Beziehungen.
Dafür ist Vererbung da. Und wenn wir jetzt sagen,
es gibt die Unterscheidung zwischen Typ und Implementation,
dann können wir eben halt auf der Typ-Ebene Hierarchien betrachten.
Und wir können auf der Implementationsebene Hierarchien betrachten.
Wenn wir auf der Typ-Ebene bleiben,
also nur Interfaces zum Beispiel angucken würden oder auch meinetwegen die Schnittstellen,
die Typen von Klassen,
dann reden wir über Operationen
Auf der Typ-Ebene haben wir alles nur Operationen.
Und wenn wir auf der Implementationsebene sind,
also wenn wir wirklich konkrete Methoden haben, also Methoden,
Rümpfe, die wirklich was tun, dann sprechen wir auch über Methoden,
die da irgendwo in Hierarchien hängen.
Und das trennen wir relativ systematisch, sauber. Wir gucken also erst mal,
wie das auf der Typ-Ebene aussieht und wie das auf der Implementationsebene aussieht.
Und was dahinter steht, warum wir diese Unterscheidung machen,
ist unter anderem ein klassischer Artikel von Kadeli und Wegner.
Das ist jetzt so ein bisschen Hintergrundinformation, das ist ein relativ sagen wir,
nicht leicht zu lesender Artikel, zumindest im hinteren Teil.
Das ist ein relativ langer Artikel, das sind irgendwie,
ich weiß nicht, hier so 50 Seiten,
on understanding types, data Abstraction, Polymorphism, uralter Artikel,
uralt aus Informatik-Sicht.
Und da haben sie eigentlich prägend Begriffe in die Welt gesetzt.
Sie haben nämlich gesagt, Polymorphie, was ist Polymorphie?
Naja, sie unterscheiden es in universelle Polymorphie und ad hoc Polymorphie.
Kann man erstmal so hinnehmen. Und die universelle Polymorphie,
die haben sie aufgespalten in parametrische Polymorphie und in Inklusionspolymorphie
Das sind Begriffe, die euch erstmal so nichts sagen,
aber im Grob kann man sagen, die Parametric Polymorphyson,
der Parametric Polymorphy, die parametrische Polymorphie, das ist das,
was man mit Generizität machen kann in Java.
Und alles das, was wir mit Vererbung machen können,
ist hier mehr oder weniger Inclusion-Polymorphie. Diese beiden Dinge hier ad hoc,
Polymorphie, Overloading und Corursion, das sind so Randthemen,
Typumwandlungen für primitive Typen, das habt ihr so ein bisschen gesehen,
die primitiven Typen in Java,
kann man irgendwie automatisch umwandeln oder eben halt erzwingen,
dass ein Float in einen Int umgewandelt wird,
das sind so Sachen, die sind sehr ad hoc,
da gibt es so ein paar Sprachregeln kann man dann ausgewählte Typumwandlungen vornehmen.
Das ist bisher noch nicht so durchgängig so,
dass man sowas auch universell machen kann.
Auch Overloading ist etwas, was ihr kennt.
Man kann mehrere Methoden in einer Klasse gleich benennen.
Hauptsache, sie unterscheiden sich in ihrer Signatur. Okay,
das ist aber nur am Rande wichtig. Also diese Unterscheidung
ist relativ zentral und sie ist deshalb auch so wichtig,
weil als ich zu dem Thema promoviert habe,
Mitte der 90er, da habe ich mich mit objektorientierten Sprachen beschäftigt.
Ein Kollege hat sich mit funktionalen Sprachen beschäftigt.
Wir waren beide wild am Promovieren,
haben uns mal unterhalten und haben irgendwie uns gar nicht unterhalten können,
weil wir irgendwie ständig gemerkt haben,
wir reden aneinander vorbei
Und das lag daran, dass diese Welten, Programmiersprachenwelten,
einfach auch einen anderen Sprachschatz haben in weiten Teilen.
Und wenn die funktionale Welt von Polymorphie redet, dann meinen die implizit,
und das war aber nicht so explizit klar, parametrische Polymorphie.
Und wenn wir in der Objektorientierung über Polymorphie reden, meinen wir eigentlich immer Inklusionsproblemafie.
Und das haben wir erst einen Augenblick für gebraucht,
bis wir das rausgefunden haben, dass wir eigentlich ineinander vorbeigeredet haben.
Also das ist tatsächlich nicht ganz leicht. Und wer da
vertieftes Interesse hat, den Artikel unbedingt lesen,
zumindest die ersten, weiß nicht, 10,
20 Seiten,
wo sie sehr ausführlich Begriffe klassifizieren
Was für uns jetzt wichtig ist, ist genau dieser Aspekt Inclusion,
Polymorphie aus Katelli Wegner, Subtyp, Polymorphie.
Und das nennen wir jetzt im Folgen einfach Polymorphie.
Und das ist eben halt sehr eng mit dem Konzept von Ersetzbarkeit verknüpft.
Also die Variable, eines Supertyps, kann auf Exemplare von Subtypen verweisen.
Haben wir jetzt ja eben gesehen. Der super Typ war
zum Beispiel Titelliste und ich kann dann mit einer Variablen von
diesem Typ auf etwas verweisen.
Das heißt, das ist auch sehr eng verknüpft mit
dieser Unterscheidung zwischen statischem und dynamischem Typ
Man braucht dafür das dynamische Binden, also um Subtyp-Polymophie praktizieren zu können,
in einer Premiersprache braucht man dieses dynamische Binden.
In Java ist fast alles dynamisch gebunden, schwupps, können wir also selbstverständlich machen.
Kein Wunder, Java ist eine objektorientierte Sprache.
Steck in den Genen. Und das ist eben halt tatsächlich
dieses zentrale technische Konzept von objektorientierten Sprachen. Und es ist die Voraussetzung für zwei Dinge,
die wir uns schon angesehen haben, beziehungsweise noch ansehen werden,
nämlich für Typ-Hierarchien und für Typ-Abstraktion, die werde ich jetzt erläutern.
Und ich gebe hier So eine sehr eigenartige Übersicht. Diese Übersicht folgt keiner formalen Notation,
ist also von mir sozusagen wild ausgedacht, was da,
also was hier, was bedeutet, ob das rund oder eckig ist.
Ich habe da eine gewisse Meinung dazu, aber das ist,
wie gesagt, es ist nicht UML oder ähnliches,
es ist einfach nur so ein Versuch,
so die klassischen Begriffe, die mit Vererbung verknüpft werden,
mal so auf einen Blick zu haben.
Und was ihr in ganz vielen Lehrbüchern lesen werdet zur Vererbung,
ist immer so was wie, naja,
es geht um Klassifizierung.
Generelle ist eine Beziehung
Das ist, dafür ist Vererbung da, ja. Dann wird immer als erstes Beispiel gebracht,
ja, stellt euch vor,
es gibt ja irgendwie so die Tiere und dann kann man jetzt klassifizieren,
Tiere,
gibt es dann sowas wie Säugetiere und bei den Säugetieren gibt es dann Hunde und Katzen und ja,
das sind dann so diese klassischen, ist ein,
beziehungsweise ein Hund ist ein Säugetier und ein Säugetier ist ein Tier und so weiter.
Und da werde ich gleich nochmal drauf eingehen, aber das ist genau das,
was man mit Vererbung in Objektnotierten Sprachen nicht macht.
Das ist genau das, was man nicht damit macht.
Das ist der Grund für diese unglaubliche Verwirrung in der Programmierwelt
Dass das Missverständnis den Leuten dann immer so beigebracht wird.
Bei mir hört ihr was anderes. Was sehr zentral ist,
was man sehr viel in Programmiersprachen macht, ist,
man baut Typhierarchien. Das sind auch, ist eine Beziehung,
aber sehr eingeschränkte, ist eine Beziehung mit bestimmten Randbedingungen.
Nicht generelle ist eine Beziehung. Dann Typ Abstraktion, das habt
ihr schon gesehen. Eine Schnittstelle beschrieben ist unterschiedlich implementiert.
Die Implementsbeziehung habt ihr schon viel gesehen. Und, was
wir uns auch ansehen werden, ist das hier Code Wiederverwendung.
Das ist die Idee, naja,
ich habe zwei Implementationen für denselben Typ,
für dieselbe Schnittstelle
Und ich stelle fest, oha, die sind ziemlich ähnlich in weiten Teilen.
Ich kann Teile des Codes sind eigentlich redundant. Schreib hier der Klasse,
Teile, die sind genauso aus wie Teile in der Klasse.
Dann versuche ich doch mal diese Redundanz zu vermeiden,
indem ich das in eine abstrakte Klasse raufziehe.
Das ist die Idee von Code Wiederverwendung. Und das ist erst mal
so der grobe Überblick. Jetzt mache ich erst mal,
schmeiße ich erst mal, schiebe ich was zur Seite,
was ich nicht hier vertieft behandeln will,
weil es eben halt nicht viel mit OOP zu tun hat.
Und das ist genau dieses Konzept von Klassifizierung.
In der objektorientierten Modellierung ist das extrem wichtig,
dass man klassifiziert,
dass man klassifizierend auf Dinge draufschaut
Was, wie gesagt, immer gern angefangen, Taxonomien in der Biologie,
Ontologien, semantische Netze, so Begriffe aus der KI.
Ja, und da gibt es dann immer so diese klassischen Standardbeispiele,
die dann in Papers auf Konferenzen diskutiert werden.
Ja, wie ist das denn? Ein Quadrat, ist
doch ein Rechteck. Wenn ich euch jetzt fragen würde,
ist ein Quadrat ein Rechteck, würdet ihr sagen, ja,
ja klar, ist ein Quadratenrecht, ist ein spezielles Rechteck.
Aber sobald ich anfange, dass objektorientiert zu modellieren,
dann kommt sehr darauf an, was ich eigentlich erreichen will mit meiner Modellierung.
Wenn ich jetzt also zum Beispiel vorstelle,
ich habe eine Klasse Rechteck und die hat zwei Operationen,
da kann ich getrennt,
Seitenlänge eins und Seitenlänge
zwei verändern
Kann ja durchaus sinnvoll sein, dass ich das bei einem Rechtdeck mache.
Dann ist es ziemlich blöd, wenn dann ein Quadrat,
ein Subtyp von einem Rechtdeck ist,
weil dann könnte ich ja Polymorph über eine Variable vom Typ Rechteck
auf einen Quadrat zugreifen und könnte dann nur eine Seitenlänge verändern,
was bei einem Quadrat aber nicht geht.
Ich kann immer nur beide Seitenlänge verändern. In dem Moment
bricht das einfach. Genauso sowas wie ein Emus, ein Forum.
Normalerweise kann ein Vogel fliegen, ein Emu kann nicht fliegen.
Also ist das irgendwie komisch. Also immer so diese Einschränkung,
wenn etwas ist, aber weniger kann als der super Typ
, dann ist das komisch
Und solche Gegenbeispiele gibt es ganz viele und die sind
viel diskutiert worden und wir sind längst darüber hinweg.
Also diese Unterscheidung, in diese verschiedenen Implementations- oder Vererbungskonzepte,
die gibt es schon ziemlich lange in der Literatur.
Und da gab es unter anderem diesen Artikel hier, Subclassing,
Ungleich-Subtyping, Ungleich-Iser. Also die allgemeine ist eine Beziehung,
da muss man sehr vorsichtig sein. Wir gucken uns an,
Subtyping und Subclassing, Typ-Hierarchien, Subtyping, Subclassing, Implementationshierarchie.
Und Dann Typ Abstraktion habe ich jetzt schon mehrfach erzählt,
brauche ich jetzt nicht noch mal was zu sagen.
Ist also schon dran gewesen. Das heißt,
was wir uns jetzt frisch ansehen, was sozusagen was Neues ist,
was ich jetzt hier einführe, das sind die sogenannten Typ Hierarchien.
Und das ist etwas, was wir sehr bewusst in SE1
vermieden haben, mit euch zu diskutieren.
Was wir mit euch diskutiert haben, war Typ Abstraktion,
also Interface, implementierende Klasse.
Jetzt wollen wir tatsächlich Typen in Hierarchien betrachten.
Also was passiert, wenn ich Typen in einer Hierarchie habe?
Erstmal begrifflich, wir machen hier einfach eine Begriffsfestlegung,
wir nennen hier in SE2,
die dann
Super- und Subtypen
Also ein Subtyp muss mindestens alle Operationen seines Supertyps definieren.
Also wenn ich einen Typ habe, dann muss ein Subtyp
die Operationen alle von dem Supertyp haben und kann eventuell welche dazufügen.
Er kann auch weitere Operationen hinzufügen. Das ist häufig so,
dass man bei Typ Hierarchien sowas macht. Auf jeden Fall
muss aber immer Ersetzbarkeit gelten. Also ein super Typ,
wo etwas erwartet wird, was man einem Supertyp sein soll,
da kann ich alles einsetzen,
was von einem der vielen Subtypen ist
Und dieses Bilden von Typhierarchien wird auch Subtyping genannt.
Und das basiert auch sehr stark auf Subtyp-Polymophie. Da werde
ich nach der Pause noch ein bisschen mehr zu sagen.
Ein Beispiel, was sehr prominent ist, sind die Typhierarchien im Java Collections Framework.
Das Java Collections Framework haben wir euch in SE1 vorgestellt,
als, da gibt es Set, da gibt es List,
da gibt es vielleicht auch noch Q und Stack.
Aber wir haben euch überhaupt nichts erzählt über Typhierarchien.
Die standen so richtig schön nebeneinander, diese Typen.
Die konnte man alle benutzen. Es gab zu jedem Interface,
gab es dann Implementationen. Zu List gibt es zwei,
zu Set gibt es mindestens zwei.
Also das habt ihr alles gesehen
Was wir verschwiegen haben und was wir uns jetzt mal angucken,
ist, dass tatsächlich diese Typen, also diese Interfaces,
Setlist, Q, die hängen in einer Typhierarchie.
Ich kann also hier tatsächlich mir mal die,
die API dazu angucken, Interface Collection.
Und dann sieht man hier all known subinterfaces,
als dann zum Beispiel hier List, Q, Set,
also die Interfaces sind Subinterfaces.
Das heißt, sie sind Subtypen von Interface Collection.
Und das ist eigenartig
Das ist eigenartig. Das ist eigenartig, warum? Das zeige ich jetzt,
weil ich nämlich hier noch ein Blue-Jay-Projekt dabei habe.
Blue-Jay, genau. Ja, ich sehe schon, die Begeisterung ist groß.
Hier ist dieses Blue-Jay-Projekt. Das ist ein sehr einfaches Projekt,
hält nur eine kleine Klasse mit einer statischen Methode.
Statische Methoden kennt ihr aus SE1. Hier benutze Collection.
Da wird eine Collection übergeben. Also jetzt tatsächlich der super
Typ von diesen ganzen Sammlungstypen, die ihr so kennt.
Collection von String
Irgendeine Collection, also eine Sammlung von Strings. Man weiß nicht,
ob es eine Liste ist, ob es eine Menge ist,
ob es ein Q ist, ist einfach nicht klar.
Aber ich kann tatsächlich, und das liegt der Typ Collection fest,
statisch, der Typ Collection sagt, man kann Ad aufrufen.
Und das Ad macht irgendwas. Das kann man also
auch hier nachlesen in der Doku. Da gibt es irgendwo
nicht schwindelig werden. Hier gibt es Ad. And sures
that this collection contains the specified element optional operation.
Oho, also eine optionale Operation. Also im Grunde genommen
hier so, mit Ad wird garantiert,
dass hinterher das Element drin ist
Okay. Sag also nichts über Duplikate oder so aus,
weil auf der allgemeinen Ebene von Sammlungen kann man da gar nichts dran zu sagen.
Okay, also kann ich hier tatsächlich das so aufrufen.
Das ist also eine Operation, die ich hier aufrufe und
welche konkrete Methode aufgerufen wird, hängt wieder davon ab,
was ich jetzt hier als konkretes Objekt übergebe.
Also hier werden jetzt in diese Sammlung werden Peter Paul,
Mary Paul und John reingefügt und dann wird diese Collection irgendwie hier ausgegeben, Collection to String.
Das ist also eine wirklich sehr einfache Quelltext. Ich hoffe,
es ist nachvollziehbar, was da passiert, in einer Collection
werden fünf Strings eingefügt und dann wird die auf der Konsole ausgegeben.
So, jetzt erzeuge ich mir mal hier so ein,
naja, machen wir erstmal hier die Direkteingabe auf.
Ich mache mir mal ein New, da war You Till Hash.
Nee, machen wir erstmal eine Linklist. Linked lest von String,
ja, kann ich ja einfach aufrufen,
ist eine Exemplarerzeugung, ich kriege eine Referenz zurück,
diese Referenz kann ich mir hier mal hinlegen und schon habe ich hier meine Linked lässt.
So, das ist also erst mal hier mein eines Exemplar.
Dann kann ich mir hier auch erzeugen, eine New Java,
You Till, Hash Set, auch wieder vom Elementtyp.
String, ich möchte also Strings reinpacken können,
zeugt mir hier so ein Exemplar,
hab wieder schon so einen Behälter, kann mit denen hier rüberziehen.
Also hier irgendwie ein Set. So, kann mich gleich
noch mehr erzeugen. Jetzt kann ich hier Benutzer Crection
aufrufen und jetzt übergebe ich eine Referenz, der Typ,
der formale Parameter ist Collection of String, eine Sammlung von Strings.
Also kann ich hier eine, zum Beispiel eine Liste von Strings übergeben.
Diese Referenz kann ich übergeben. Wenn ich das jetzt mache,
jetzt muss ich nochmal eben dafür sorgen, dass hier, sorry,
dass hier diese Konsole, dass jetzt das nicht gelöscht wird,
weil ich will dir gleich zeigen, dass die Aufrufe unterschiedliche Ergebnisse zeigen.
Also ich rufe jetzt hier auf, benutze die Collection und übergebe hier eine Liste.
Und wenn ich auf einer Liste die Dinge einfüge,
dann werden die mehr oder weniger genau in der Reihenfolge da angezeigt,
wie sie auch eingefügt wurden.
Also wir können uns den Quertext ansehen. Ja,
also hier Peter, Paul, Mary,
Paul und John
Und was kommt da raus? Peter, Paul, Mary,
Paul und John. Das ist also genau die Reihenfolge,
wie ich sie auch eingefügt habe.
Bei einer Liste ist das ja durchaus sinnvoll.
Ed fügt ja bei einer Liste am Ende an. Okay,
jetzt mache ich mal das Gleiche. Sag also jetzt hier,
benutze doch mal eine Collection und Füge-Elemente rein in ein Set.
Dann wird hier jetzt, wird das in das Set reingefügt.
Und direkt danach wird das Set mit Print-Turn ausgegeben.
Dann kommt hier sowas raus. Paul, Mary,
Peter und John. Vier andere, also vier,
erstmal eine andere Reihenfolge.
Und Duplikate sind nicht zugelassen
Der Klient macht das Gleiche, aus Sicht des Klientes ist
derselbe Quelltext. Es wird irgendwas in eine Collection reingepackt.
Es werden fünf Strings in eine Collection reingepackt,
aber man weiß nicht, was für eine konkrete Collection es ist.
Und die können sich tatsächlich sehr unterschiedlich verhalten. Das ist tatsächlich
jetzt hier das erste Mal, dass ihr sowas seht.
Eigentlich war es bisher so, wenn ihr sowas hattet,
ein Interface, mehrere Implementationen, dann sollten sich die Implementationen alle gleich verhalten.
Möchte man gerne. Das Verhalten soll gleich sein,
die Implementation kann unterschiedlich sein. Bei Typ Hierarchien kann das jetzt durchaus sein,
dass der super Typ sehr großzügig ist in der Definition der Semantik,
aber die die konkreten Untertypen dann eben halt das einschränken.
Und ich könnte jetzt hier auch noch einen weiteren Aufruf machen
mit einem weiteren Exemplar, zum Beispiel mit einem Stack.
Und das würde wieder sich irgendwie etwas anders verhalten. Okay,
das also nur, dass ihr das mal gesehen habt,
das sind dann tatsächlich echte Typhierarchien.
Weiteres Beispiel ist die Exception Hierarchie in Java,
die werden wir uns in zwei Wochen, nee,
in drei Wochen angucken oder in zwei Wochen, nee, in zwei Wochen.
Da haben die nicht in Java auch was richtig Schickes gemacht,
die haben sozusagen diesen Fehlerbehandlungsmechanismus Exception, der sehr gut gebaut ist in Java,
den haben sie verknüpft auch mit der Möglichkeit,
Polymorphie zu verwenden.
Und das führt dazu, dass man dann tatsächlich Fehlerbehandlung sehr differenziert machen kann.
Also je nachdem, welche Art von Fehler es ist,
welchen Typ einen Fehler hat, kann ich unterschiedlich reagieren.
Ganz heißer Scheiß und das gucken wir uns aber erst in zwei Wochen an.
Gut, nächste Woche gucken wir uns intensiv an Code-Wiederverwendung,
also dass all das, was mit Implementationsvererbung zu tun hat.
Und das ist auch Subclassing im Allgemeinen oder in Heritance,
wenn ihr in englischen Artikeln was lest über Implementationsvererbung,
da steht dann meistens in Heritance
Vererbung in Heretance, gegenüber Subtyping oder Polymophism. Und da
ist es wieder so, wir haben eine Hierarchie von Implementationen,
das heißt eine Subklasse erbt und wir reden jetzt über die Methoden und die Felder,
ihre Superklasse.
Da muss man sehr differenziert draufschauen. Da wird also relativ viel gemacht.
Die geerbte Klasse kann jetzt sehr viel machen mit dem, was sie erbt.
Sie kann Methoden definieren, redefinieren, überschreiben, erweitern.
Also das ist sozusagen wie, da ist irgendwas vorgegeben.
Ich habe von einer Superklasse und das,
was ich bekomme, was ich erbe,
das kann ich dann aber irgendwie für meine spezifischen Zwecke anpassen
Das ist auch in Java realisiert über dynamisches Binden.
Zufällig ist das der gleiche Mechanismus in der Sprache.
Theoretisch ist es aber auch anders machbar und es ist tatsächlich
auch mit mindestens einer Sprache anders gebaut worden.
Die haben einfach Quelltext geerbt, also Quelltext kopiert. Die haben also gesagt,
wenn ich hier diese Klasse von dieser Klasse was erbt,
dann kann man einfach sagen,
ich kopiere den Quelltext hier einfach runter und wenn ich hier
irgendwie Methoden sozusagen verändern will,
dann schmeiße ich einfach die Methoden von da oben
raus und nehme jetzt meine Methoden da rein
Das kann man so sehen. Das ist ein mentales Modell,
was man haben kann, um Code-Wiederverwendung zu verstehen.
Das gucken wir uns, wie gesagt, nächste Woche ausführlich an.
So, jetzt haben wir noch ein paar weitere Begriffe,
die ich einfach hier nochmal so ein bisschen einführe,
zum allgemeinen Teil über Vererbung.
Erstmal versuche ich durchgängig hier die Begriffe konsistent zu verwenden.
Das heißt, wenn wir auf Hierarchien von Tüten schauen,
dann reden wir über Super- und Subtypen.
Und wenn wir auf Hierarchien von Implementationen schauen,
also auf Klassen,
dann reden wir von
Ober- und Unterklassen
Einfach ein bisschen klarer, um das ein bisschen deutlicher abzugrenzen.
Was relativ trivial ist, ist, dass solche Subtyp- und Unterklassenbeziehungen,
solcher Vererbungsbeziehungen unter anderem transitiv sind. Wenn ich sowas habe,
hier C, Erbt von B,
egal ob es jetzt Subtyp ist oder Unterklasse, wie auch immer,
C ist ein Subtyp von B und B ist ein Subtyp von A,
dann gilt transitiv auch, dass C ein Subtyp von A ist.
Und um dann etwas genauer benennen zu können,
was wir jetzt konkret meinen,
fühlen wir noch den Begriff der unmittelbaren Subtyps
ein oder unmittelbare Beziehungen
Das hier ist ein unmittelbarer, super Typ.
Also B ist ein unmittelbarer, super Typ von C.
Aber A ist nur ein allgemeiner Supertyp von C. Gut.
Dann die Begriffe einfach und mehrfach Vererbung. Ich weiß nicht
, inwieweit ihr schon euch mit Vererbung beschäftigt habt.
In Java ist es so, dass es nur einfache Vererbung
gibt für Implementationen, also für Klassen.
Aber es gibt Mehrfachvererbung für Interfaces. Einfach Vererbung heißt,
dass es da links angedeutet, dass Jede Klasse oder jeder
Typ in der Hierarchie nur genau einen Elternknoten hat.
Es gibt nur genau einen Vater oder eine Mutter hier.
Das ist dann sowas Baumartiges. Es können natürlich mehrere Supertypen
sein, aber nur ein unmittelbarer Supertyp.
Und mehrfach Werbung heißt einfach, na, ich kann da mehrere haben.
Sowas gibt es durchaus auch. Und in Java ist das
insbesondere möglich mit Interface, das werden wir uns heute nochmal
ein bisschen genauer angucken
Also Java, Einfachvererbung bei Klassen und Mehrfachvererbung zwischen Interfaces.
Es gibt Sprachen, die bieten Mehrfachvererbung auch für Klassen an.
Also für Implementationen. Und da kann man wieder so ein bisschen ausholen und
so ein bisschen die Geschichte der Programmiersprachen gucken, wie wichtig Mehrfachvererbung ist.
Das wurde also sehr, sehr intensiv diskutiert. Wenn man so ein bisschen guckt,
wie die O-Sprachen entstanden sind in den 60er Jahren,
Simular, war so der Urvater der Objekte und denen Sprachen,
dann kam in den 60er Jahren Simular,
in den 70er Jahren kam Smalltalk,
ist quasi ein Urvater der objektantierten Programmierung, Anfang der 80er Jahre,
ging der Markt dann auf.
Dann kamen plötzlich ganz viele Objekte und Hintersprachen. Und dann
kam Mitte der 80er, kam dann Bertram Meier mit Eifel.
Eine Programmiersprache, die damals ganz neu war. Und der hat gesagt,
ja, für vernünftige Software Engineering braucht man multiple Vererbung.
Man braucht multiple Vererbung. Und zwar multiple Implementationsvererbung. Der war
sehr überzeugend, der Mann, der trat sehr selbstsicher auf.
Und die ganze Community saß wie das Kaninchen vor der Schlange.
Und Anfang der 80er hat zum Beispiel Bjarne Strauß Trupp ein
sehr schlauer Mensch,
CPlus plus definiert
Und zwar hat sie hergenommen und gesagt,
wir müssen in C jetzt mal diese ganzen neuen Konzepte der Objektorientierung,
müssen wir da mit reinnehmen.
Also irgendwie C Plus plus war so C plus Objektorientierung.
Auch noch andere Sachen, aber vor allen Dingen Objektorientierung damals.
Und 10 plus plus war in den 80er Jahren,
hatte auch nur einfache Implementationsvererbung.
Dann kam Bert Horn Meier mit seinem Eifel und oh,
der Markt, der Markt macht das Geschäft.
Das heißt also, da sagt jemand, wir brauchen multiple Vererbung.
Das schien überzeugend, also hat der Bjarnes Draußen,
hat gesagt, das kann ich auch.
Hat dann Ende der 80er in C plus plus
multiple Implementationsvererbung reingebaut
Und hat sich, finde ich, damit ziemlich ins Knie geschossen,
weil dann in den 90er Jahren so die Sprache,
die dann kam, unter anderem Java,
die waren wieder etwas schlauer.
An dem haben die gesagt, oh Gott, oh Gott,
multiple Implementationsvererbung, das ist so kompliziert, das wollen wir nicht.
Das macht das Leben nur schwer, schmeißen wir wieder raus.
Also auch C-Sharp als Nachfolger von Java hat keine multiple Implementationsvererbung.
Und bei C-Plus-Plus gibt es ungefähr fünf Menschen auf diesem Planeten,
die verstehen, wie mehrfache Implementationsvererbung in C-Plus-Plus wirklich funktioniert.
Also ich habe immer gern den Challenge rausgegeben,
zeigt mir ein Killerbeispiel, wo ich sehe,
ich brauche unbedingt multiple Implementationsvererbung
Wenn ihr mir irgendwas zeigen könnt, dann,
also ich habe es bisher nicht gesehen,
also Bjarnes Graustrupp ist da, glaube ich, auch durchaus kritisch mit.
Also da ist die Welt, hat sich so ein bisschen wieder, hat gelernt.
Ganz, ich gehe auf die Probleme nicht ein,
weil das wirklich über Java White hinausgeht.
Aber so grob, es geht da um Namenskollisionen, die da stattfinden.
Und zwar einmal so einmal auf diese Art und Weise.
Ich habe hier zwei unabhängige Klassen. Und die definieren beide
eine Eigenschaft A. Und jetzt erbe ich in C von beiden
Klassen oder von beiden Typen.
Und was ist jetzt mit dem A? Was passiert mit
dem A? Ein wunderschönes Beispiel habe ich natürlich zufällig dabei.
Das ist hier dieses
Cowboy und Window. Was hat ein Cowboy mit einem Window zu tun?
Nicht sehr viel. Sollte man meinen. Ich zeige mal,
was ein Cowboy kann, ein Cowboy irgendwie in einer Simulation.
Der hat nur eine Operation, Draw. Ich weiß nicht,
ob ihr wisst, also wenn Cowboy seinen Revolver zieht,
dann ist das to Draw als Colt.
Okay, also Draw ist so, zieh den Colt.
Und bei einem Window, ja, so einem Fenster möglicherweise,
einer grafischen, in einem grafischen System, Interface Window,
Draw, this Window with its Current Content.
Also so bei gut Graf von letzten Sommerflächen aktualisieren eines Fensters oder ähnliches.
Also hier einfach nur ein Draw. So, das sind offensichtlich
sehr verschiedene Operationen. Sehr unterschiedlich. Haben nichts miteinander zu tun.
Ich kann jetzt aber in Java dieses Interface hier definieren.
Cowboy Window. Oder das ist eine Klasse.
Das ist eine Klasse sogar. Klaas, Cowboy,
Cowboy Window, Implements, Cowboy und Window.
Und damit diese Klasse als eine Implementation von diesen beiden Interfaces
akzeptiert wird, muss der hier nur eine Methode implementieren,
nämlich Draw
Er darf sogar nur eine Methode Draw implementieren. Das heißt,
in Java gibt es keine Möglichkeit, diese beiden sehr unterschiedlichen Draw-Methoden zu unterscheiden.
Es ist die gleiche Signatur, Draw, der Name ohne Parameter,
gleiche Signatur. Also ist es dieselbe Operation. Das heißt,
Java identifiziert diese beiden Operationen und sagt, das ist das eine.
So, und nun, lieber Cowboy, Window gibt immer Mühe,
das beides zu implementieren. Ist sehr schräg. Und das ist eben halt,
sagen wir, eine der komischen Ecken von Dava,
wo man sagen kann, wow, ob das so glücklich ist,
trifft meistens nicht auf Probleme, weil man ja,
wenn man es weiß, kann man es ja vermeiden,
dass das zu echten Problemen führt.
Aber es sollte einfach klar sein, dass da einfach ein
Automatismus stattfindet in der Sprache, der nicht unbedingt immer greift.
So, dann ein anderer Fall, Diamantvererbung. Also ich
habe irgendwo eine Klasse, dann habe ich zwei Klassen,
die von dieser Klasse erben und dann habe ich eine vierte Klasse,
die von den beiden erbt. A, definitiv irgendeine Eigenschaft,
A, es kann ein Feld sein,
es kann eine Methode sein, jetzt redefinieren, wahrscheinlich Methoden,
B und C, diese Methoden, machen da irgendwas anderes draus,
verändern die, was passiert jetzt,
wenn das zusammengeerbt wird
Was ist jetzt hier das AND? Sehr komplizierte Fragen in verschiedenen Sprachen
sehr unterschiedlich gelöst. Habe ich in meiner Diss alles beschrieben,
interessiert euch wahrscheinlich nicht so sehr. Also deswegen jetzt vor
der Pause erstmal die Zusammenfassung, was ich versucht habe,
klarzumachen, Vererbung ist schon eine zentrale Eigenschaft in objektnotierten Sprachen.
Ist auch ein, dummerweise aber auch ein sehr stark missbrauchter
und missverstandener Mechanismus. Ist stark überladen,
man kann ziemlich viel machen mit Vererbung und die Konzepte,
die für uns am wichtigsten sind, sind einmal subtypolimophie,
Hierarchien zu bilden und auch um Typ Abstraktionen zu ermöglichen.
Das, was wir schon kennen aus SE1. Und außerdem möchte man Implementationsvererbung haben,
weil das in vielen Stellen Redundanz spart und man eben halt
sehr flexibel dann ausführbaren Quelltext kombinieren kann.
Gucken wir uns nächste Woche an. So,
nochmal einfach jetzt gesagt, die Unterscheidung, die ich hier vornehme,
die ist sehr genau oder sehr ausführlich.
Und wenn ihr später wie in Java programmiert,
dann werdet ihr irgendwelche Klassen voneinander erben lassen wahrscheinlich und dann passiert meistens beides.
Also eine Klasse erbt ja von ihrer Superklasse sowohl den Typ als auch die Implementation.
Also es tritt häufig zusammen auf. Aber ihr solltet die
Unterschiede kennen. Damit ist einfach kompetent verarbeiten könnt. Okay,
sind bis hierhin noch Fragen, irgendwie jetzt zu dem,
was ich hier erzählt habe.
Es war ja vor allen Dingen ein bisschen Übersicht und Begrifflichkeit.
Also nichts, was jetzt irgendwie komplett überraschen sollte.
Keine Frage, dann machen wir fünf Minuten Pause.
Wir gucken uns jetzt nochmal, was ist das Thema Subtyping,
etwas genauer an. Nochmal, was ich eben schon gesagt habe,
was passiert beim Subtyping? Es werden allgemein Typen in Hierarchien
angeordnet. So, und wenn ich vorhin gesagt habe,
diese Ist-Ein-Beziehung, nicht alle ist Einbeziehungen, sind Subtyp-Beziehungen,
dann ist das auch richtig.
Aber umgekehrt gilt auf jeden Fall, jede Subtyp-Beziehung sollte,
als ist eine Beziehung formulierbar sein. Das heißt,
wenn ein Typ, ein Subtyp von einem anderen ist,
dann sollte man immer sagen können, der Subtyp ist ein super Typ.
Das ist einfach deshalb wichtig, weil man Ersetzbarkeit garantiert haben will.
Das heißt, die Subtypen sollen überall da einsetzbar sein,
wo etwas vom Supertyp erwartet wird
Und das ist eine sehr technisch enge Anforderung. Und erst,
wenn man diesen technischen Korridor sozusagen gut beherrscht und gut versteht,
dann kann man auch fachliche Hierarchien, die es ja durchaus gibt,
es gibt ja durchaus in Anwendungsbereichen fachliche Hierarchien, dann kann man die auch vernünftig abbilden.
Also dann weiß man sozusagen, welche Hierarchien man gut mit Vererbung abbilden kann und welche eher nicht.
Hier mal so ein Beispiel. Wir sind in einem Bürokontext,
wir müssen also für eine Verwaltung,
Software schreiben und modellieren dann die Umgangsformen, die da so in der Verwaltung,
in einem Büro existieren und stellen dann fest, aha,
wir legen irgendwie fest, wir stellen fest,
es gibt sowas wie Stapel, die benutzen ganz viele Stapel,
die haben ganz viele Stapel auf ihren Schreibtischen,
die benutzen sehr viele Ordner,
die benutzen auch Boxen und die haben bestimmte Ablagen.
So, und wenn man sozusagen erstmal so die Anforderungen aufgenommen hat,
dann so die Umgangsform festgehalten hat, stellt man fest,
oha, hier gibt es relativ viel, was alle diese Materialien können,
also ein Dokument einfügen, Dokument auswählen und so weiter.
Und dann versucht man diese Gleichartigkeit irgendwie hochzuziehen.
Man baut also eine Typhierarchie. Man sagt, na gut,
sowas wie eine Ablage, da kann man ein Dokument einfügen,
auswählen, entnehmen. Und dann ist es so,
dass tatsächlich die Subtypen hier, also Stapel, Box und Ordner,
das sind Subtypen von Ablage, das ist der Oberbegriff,
das sind die Unterbegriffe, dass dann diese Begriffe,
alle die Operationen anbieten müssen, die auch der Supertyp definiert.
Also tatsächlich implizit hat ein Stapel dann nicht nur Stapel drauflegen,
Auflösung ausbreiten als Operation, sondern natürlich auch alles,
was die Supertypen definieren.
Das ist einfach so festgelegt bei Typhierarchien
So, und jetzt gucke ich hier so drauf und sehe,
aha, oho, Stapel, Box.
Und dann sehe ich aber hier noch eine Gemeinsamkeit.
Box mit Textbeschriften und Ordner, auch mit Textbeschriften.
Man kann die also irgendwie da so, kann da
was draufschreiben, was machen die in dem Büro-Kontext so,
wo ich dir jetzt diese unterstützende Software bauen soll.
Also mache ich mal sowas. Box, das ist
irgendwie mit Textbeschriften und dann Ordner ist dann eine Box.
Also man muss immer sagen können, ist ein,
also ein Stapel ist eine Ablage,
eine Box ist eine Ablage und ein Ordner ist eine Box.
Jetzt von unserem natürlichen Verständnis, wenn wir sagen,
nee, das ist irgendwie komisch.
Ein Ordner ist keine Box. Das heißt, technisch ist das
korrekt. Wir haben keine Redundanz hier in unserer Typhierarchie.
Die Operationen sind alle nur einmal da.
Aber fachlich ist es irgendwie unsauber. Also jemand,
der jetzt aus dem Anwendungsbereich mit drauf schaut und würde sagen,
da hat er aber was Komisches, entworfen in eurer Software.
Eigentlich ist bei uns ein Ordner nicht eine Box.
Das sehen wir nicht so an. Das heißt,
das ist eher eine schlechte, technisch motivierte Modellierung.
Da sagt man einfach, naja, ich will aber,
dass da auch immer die Methoden nicht redundant drinstehen in den Typen.
Man kann es aber auch so machen, Ja,
und dann sagt man hier Boxen und Ordner,
das sind so Dinge, da werden Sachen so wegarchiviert,
also wie nennt man das mal mit Archiv?
Und dann kann man eben so ein Archiv mit einem Text
beschriften und dann hat man hier die Box plötzlich ohne eine Operation.
Da könnte man jetzt auch wieder sagen, technisch ist das doof,
man definiert hier einen Typ, der eigentlich gar keine weiteren Operationen definiert.
Ist aber gar nicht so selten der Fall,
dass man trotzdem so einen Typ dann definiert, um ihn einfach benennen zu können.
Also so ein Typ hat ja durchaus auch schon eine Bedeutung als Typ er selbst,
das ist ja nicht viel anders als hier in dem Collections Framework,
wenn ich hier mir die Schnittstelle angucke von Collection,
dann gibt es hier diese ganzen Operationen, nicht schwindelig werden,
at all clear contains, contains all, ist überschaubar,
sind nicht so viele, also hier irgendwo kommt jetzt auch vielleicht das Ende da, to aray gibt es da noch.
Das sind also so, weiß nicht, 12, 15 Operationen.
Und wenn ich mir jetzt anschaue, hier,
dass das Interface Set ja ein Subtyp ist von diesem Interface Collection,
hallo, kann ich das mal geladen hier?
So, dann gucke ich hier auf die Operation und siehe da,
es sind die gleichen.
Add at all clear contains und so weiter
Die Kommentare sind vielleicht ein bisschen anders,
aber es sind tatsächlich die gleichen Operationen. Das heißt,
ein Set fügt gegenüber dem allgemeinen Typ Collection nicht weitere Operationen hinzu.
Das ist anders bei der Liste. Wenn ich hier zurückgehe und
mir die Liste angucke, dann werden wirklich Operationen hinzugefügt.
Da gibt es zum Beispiel ein Ad mit einem Index.
Oder Ad All mit einem Index. Oder Get Index,
also gib mir etwas an einer Position.
Also Index basierte Operationen fügt eine Liste hinzu gegenüber einer allgemeinen Collection.
Aber ein Set fügt nichts hinzu, das heißt,
trotzdem ist es sinnvoll, den Typ Set zu benennen und zu sagen,
naja, es ist eben halt das Konzept eines Sets ist,
keine Duplikate zugelassen und so weiter.
Okay, also da, so beim Modellieren von Typhierarchien,
da muss man schon so ein bisschen aufpassen,
weil es gut ist,
wenn man einen bestimmten Anwendungsbereich modelliert in Software und dann die
Begriffe in dem Anwendungsbereich und auch die Typhierarchien einigermaßen passend zu dem,
was in der Software abgelegt ist.
Man möchte da gerne eine gewisse Strukturähnlichkeit haben,
weil das zu weniger Brüchen führt. Ich möchte ja möglichst,
dass meine Software ein gutes Modell der Realität ist,
also sollte ich dafür sorgen, dass die auch diese Begriffe gut zueinander passen.
Und wenn es da Brüche gibt, ist das meistens schlecht.
Und deswegen sollte man immer auch im Hinterkopf behalten,
was will ich eigentlich modellieren? Und wenn es fachliche Zusammenhänge sind,
dann sollte man eben auch aufpassen, dass das gut zur Realität passt. Okay,
also Subtyping in Java, jetzt nochmal so ein bisschen technischer betrachtet,
wenn man einfach nur in Java, in der Sprache da war, gucken,
was ist eine Subtyp-Beziehung, dann gibt es genau drei Fälle,
wann eine Subtyp-Beziehung auftritt.
Zum einen zwischen Interfaces, die über eine Extenzbeziehung verknüpft sind.
Das haben wir hier gerade gesehen
Ah, hier, hier war's. Oben in dem Kopf
von List steht Public Interface List, Extense Collection.
Also List, Extense Collection. Das ist das, was
hier auf der Folie steht. Das ist eine Subtypbeziehung zwischen Interfaces.
Dann kann es die auch zwischen einer Klasse und einem Interface geben.
Auch eine Klasse ist ja ein Typ. Und wenn eine
Klasse ein Interface implementiert, dann ist das auch eine Subtypbeziehung.
Und die Implementsbeziehung kennt ihr schon. Das heißt also,
auch die Implementsbeziehung ist implizit
eine Subtypbeziehung
Und dann gibt es auch noch eine Beziehung zwischen Klassen.
Wenn Klassen voneinander erben, das wird wieder mit dem Schlüsselwort Extenz ausgedrückt.
Also wenn eine Klasse von einer anderen erbt,
dann sagt sie irgendwie Extenz, eine andere Klasse.
Das heißt, das Schlüsselwort Extenz wird für zwei Zwecke verwendet,
das sozusagen das Schlüsselwort ist überladen mit zwei verschiedenen Konzepten.
Das Geschmackssache, ob das gut ist,
aber es ist immer in Java so gemacht.
Also Extenz sowohl zwischen Interfaces als auch zwischen Klassen.
Okay. Jetzt habe ich schon, glaube ich,
ein paar Mal gesagt, dass ein Interface eine
reine Typbeschreibung ist
Das heißt also, wenn ich ein Interface habe und
das steht mit irgendwelchen anderen Typen in einer Beziehung,
dann ist es immer eine Subtyp-Beziehung.
Das heißt, was man zum einen macht, ist sowas für Typ-Abstraktion.
Dafür verwendet man Interfaces sehr gern. Das habt ihr auch
in SE1 schon gesehen. Also Titelliste als Interface und dann
gibt es Implementationen davon, das nennen wir hier Typ-Abstraktion.
Kann von mehreren Klassen implementiert werden. Der Name des Interfaces
ist meist ein Substantiv. Eine Liste, eine Menge,
eine Abbildung,
das sind Substantive
Was ein weiterer, sehr häufiger Anwendungszweck ist für Interfaces.
Das sind die sogenannten adjektivischen Abstraktionen. Also dann beschreibt ein Interface
nur eine Teilfunktionalität oder nur eine Rolle. Und die Operationen
dieser Rolle werden von der implementierenden Klasse unter anderem angeboten.
Der Name des Interfaces ist dann meist ein Adjektiv.
Da habe ich auch nochmal ein kleines BlueJ-Beispiel dabei.
Auch etwas, was ihr eigentlich im Prinzip
schon kennt
Aus SE1 ganz zu Anfang. Die ersten beiden Aufgabenblätter,
ihr erinnert euch vielleicht noch, da gab es Kreis, Quadrat und Dreieck.
Und die konnten sich ja auf einer Zeichenfläche zeichnen.
Da war dann diese Leinwand dabei hier und auf der Leinwand
haben diese Objektypen sich dann selbst gezeichnet.
Und man konnte die dann irgendwie manipulieren,
man konnte die Farbe ändern und so weiter.
Was ich jetzt hier einfach mal dazu definiert habe, ist ein Interface farbig.
Also genau so ein Beispiel für ein adjektivisches Interface.
Und dieses adjektivische Interface ist hier so definiert,
dass man nur eine Operation hat, Also alles,
was vom Typ farbig ist, muss die Operation Farbe ändern anbieten.
Das war jetzt nicht sehr schwierig. Diese drei Klassen dieses
Interface implementieren zu lassen, weil die hatten alle drei schon diese Methode.
Die haben die schon angeboten, diese Dienstleistung. Das heißt,
konnte man also sehr schön sagen, hier, Kreis,
Implements, fahrig. Kreis, Implementsfarbig, also tatsächlich,
die implementieren das Interface. So, das ist jetzt auch
ein wunderbares Beispiel nochmal für Polymorphie. Für diese Subtyp-Polymorphie.
Das zeige ich jetzt einfach mal. Ich habe hier
eine Klasse Figurenerzeuger
Die ist ja einfach gebaut, eine statische Methode.
Macht einen neuen Kreis, ein neues Quadrat,
ein neues Dreieck, macht die alle sichtbar,
erzeugt dann ein Jabba You Till Set,
also ein Set von farbigen Dingen.
Das heißt also, der Elementtyp dieser Menge ist farbig.
Es müssen farbige Dinge sein. Ich kann also einfach alles,
was farbig ist, also was farbig Interface implementiert,
kann ich da reinpacken in diese Menge.
Und ich kann deswegen hier auch Kreis, Quadrat und Dreieck,
die ich hier gerade erzeugt habe,
die kann ich da einfügen,
weil sie
dieses Interface farbig implementieren
Und die Referenz auf dieses Set liefere ich dann zurück.
Das habe ich hier einfach nur gemacht, damit ich
nicht so viel Code schreiben muss jetzt in der Vorlesung.
Also wenn ich das jetzt hier ausführe, dann kriege ich hier,
die gibt Menge, liefert mir so ein Set zurück.
Okay, dann habe ich jetzt tatsächlich hier die Figuren zeichnen lassen und
ich kann mir hier die Referenz mal eben auf die Objektleiste holen.
So, jetzt habe ich hier also meine Menge mit,
size, also hier sind irgendwie drei Elemente drin, drei farbige Dinge.
Alle diese Dinge sind farbig. Und jetzt habe ich hier
eine weitere Klasse Die nenne ich mal, die habe ich
hier unfärber genannt. Auch den können wir uns kurz angucken.
Das ist ein Typ, mit dem man Dinge umfärben kann.
Also wenn Dinge, der kann einfach farbige Dinge umfärben.
Ich übergebe einem neuen Umfärber einen Farbnamen, zum Beispiel schwarz,
dann ist es ein Schwarzfärber, so ein Schwarzmaler.
Und dann habe ich hier eine Methode, also in seinem
Konstruktor kriegt er einfach hier einen Farbnamen übergeben und dann behält er dann auch.
Das heißt also, der ist immer zuständig,
alle Dinge in die gleiche Farbe umzufärben.
Und zwar gibt es dann hier eine Methode Färbe um.
Die bekommt eine Menge von farbigen Dingen
Ein Set farbig, Set of farbig und macht mit einer
erweiterten Vorschleife nichts weiter als durch diese ganzen farbigen Dinge durchzugehen.
Und deren Farbe zu ändern auf die Farbe,
die der hier so hat. Also ich kann mir hier
so ein Umpapper mal definieren und sage jetzt hier schwarz.
Ein echter Schwarzmaler. Und jetzt kann ich ja diesem Umfärber
beim Aufruf der Methode Färbe um eine Menge von farbigen Dingen.
Zufällig habe ich hier so eine Menge von farbigen Dingen rumliegen,
übergebe die Referenz hier.
Und was macht der? Der macht nichts weiter als die
ganzen Dinger schwarz zu färben
Painted Black, wie die guten Rolling Stones.
Aber hier an dieser Stelle einfach spannend zu sehen,
der Unfärber weiß nichts von Kreisen, Quadraten, Dreiecken.
Der Unfärber verwendet Polymorph, irgendwas, was farbig ist.
Und das ist tatsächlich jetzt im wahrsten Sinne des Wortes Polymorphy,
ja, Polymorph, vielgestaltig. Der Unfärber kann alle möglichen Gestalten umfärben.
Und das ist ja nur jetzt so ein sehr rausgeschältes Exemplar oder ein sehr ausgestelltes Beispiel für so ein Unfärber,
wenn ihr in Hier, PowerPoint,
wenn ich hier jetzt irgendwo mir irgendwas raussuche,
hier dieses Element und da jetzt irgendwie sage,
oder dieses hier, dann kann ich jetzt hier sagen,
ich möchte jetzt irgendwo hier die Farbe ändern,
irgendwie die Farbe des Fonds,
dann kann ich hier weitere Farben mir auswählen und habe hier so eine ganze Farbpalette.
Und da ist es auch so,
da ist ein Objekt selektiert und jetzt sage ich hier,
mach mal eine andere Farbe und diese Farbpalette weiß nichts von den Objekten,
die in der ganzen Grafikanwendung zur Verfügung stehen,
sondern der färbt einfach alles um.
In die Farbe, die da jetzt entsprechend angegeben wird
Also das ist in grafischen Benutzungssystemen, grafischen Oberflächen,
total gängig, dass man so mit Polymorphie so Dinge gleich behandelt,
ohne ihre Details zu kennen.
Das ist genau diese Idee von Polymorphie. Ich abstrahiere von
Details und interessiere mich nur für einen bestimmten Aspekt.
Und deswegen hier ein adjektivischer Aspekt ist das mit dem farbig.
Gut, dann gibt es auch reine Typhierarchien. Also was
wir gerade gesehen haben, Collections als super Typ von Listen und Mengen.
Das ist dann also auch eine Möglichkeit, wie man Interfaces gut verwenden kann.
Was ich aber auch nochmal zeigen will, ist,
ich kann ja mit Interfaces multiple Vererbung machen, also multiples Subtyping.
Das heißt, eine Klasse kann zum Beispiel mehrere Interfaces implementieren.
In diesem Beispiel ist es so, dass die alle hier
nur ein Interface implementieren. Ich habe noch ein Projekt hier,
das ist praktisch das Gleiche, kann ich das jetzt hier zumachen?
Habe wieder diese Klassen hier, die implementieren das Interface
farbig und außerdem habe ich jetzt ein weiteres Interface bewegbar.
Hat jetzt eine sehr einfache Schnittstelle nach links bewegen,
nach rechts bewegen, nach unten bewegen,
nach oben bewegen
Ein Interface, das beschreibt, das etwas beweglich ist oder bewegbar ist.
Vielleicht so eine Joystick-Steuerung oder ähnliches. Okay. Und
ich habe jetzt hier auch einen Klientenföhn sowas gebaut.
Also der kriegt hier ein bewegbares Objekt,
was irgendwie das Interface bewegbar implementiert und kann dann aufrufen nach rechts bewegen,
nach unten, nach rechts, nach unten,
der macht also eine Treppenweise hier, so ein Bewegen.
Okay. Und hier habe ich wieder meinen Figurenerzeuger,
der macht das Gleiche wie vorhin,
erzeugt mir also hier
mehrere Figuren
Und was ich jetzt nochmal zeigen kann, ist,
ich kann tatsächlich nicht nur Dreieck zum Beispiel von farbig erben lassen,
sondern auch von bewegbar.
Ich kann jetzt hier einfach in Blue Jay das direkt so hinklicken
und kann sagen, zack, Dreieck soll auch bewegbar sein.
Das muss natürlich Dreieck, alle die Operationen implementieren,
die hier gefordert sind. Zufällig ist das so,
das weiß ich. Deswegen kann ich das übersetzen.
Diese Operationen waren schon da in dem Dreieck.
Und jetzt kann ich tatsächlich von dem Figurenerzeuger hier sagen,
ach so, hier muss ich gar nicht direkt mit dem Figurenerzeuger machen.
Ich mache mir hier mal ein Exemplar von
so einem Dreieck.
Macht das sichtbar
Da ist da oben in der Leinwand angezeigt.
Und jetzt kann ich hier eine Steuerung hernehmen.
Und da kann ich jetzt sagen, bewege doch mal.
Und da übergebe ich ein Dreieck.
Auch der weiß nicht, dass es ein Dreieck ist.
Der weiß nur, es ist steuerbar, es ist bewegbar.
Und der macht jetzt hier, zack,
bewegt das Ding einmal so horizontal nach unten.
Da hat er nichts weiter zu tun gehabt,
als das zu tun. Also, ihr seht,
Dreieck implementiert jetzt hier tatsächlich zwei Interfaces.
Implements,
farbig, bewegbar
Und ein Dreieck ist farbig, ein Dreieck ist bewegbar.
Ja, in der Modellierung hier ist das eben so und
dann macht das durchaus Sinn. Also ich könnte jetzt
auch hier Quadrat und Dreieck ebenfalls von Bewegtbeierben lassen,
dann könnte ich die alle so joystickartig über den Bildschirm bewegen.
Schön, schön. Marker Interfaces,
gehe ich jetzt mal nicht so drauf ein,
das ist eher noch so ein Detail, was später kommt.
Jetzt muss ich nochmal auf zwei Begriffe eingehen,
die relativ wichtige Rolle spielen.
Einmal das Redefinieren und einmal das Redeklarieren. Redefinieren werden wir
uns auch in der nächsten Woche
nochmal genauer angucken
Das ist das Ändern der Implementation einer Operation in einer Subklasse.
Das kennt ihr schon, ja,
in der Klasse Object gibt es ja zum Beispiel das Equels.
Equels ist eine Operation des Typs Object.
Und da gibt es auch eine Implementation,
also es gibt eine Methode Equels in der Klasse Object.
Und wenn wir eine eigene Klasse haben und dann das Equels redefinieren,
das haben wir ja schon gemacht,
dann ist das tatsächlich eine andere Methode für dieselbe Operation.
Für dieses Equels, was in Object als Typ definiert,
als Operation definiert ist.
Bleibt also dieselbe Operation. Ich ändere nur
die Umsetzung der Operation
Das sehen wir uns, wie gesagt,
dann in der nächsten Woche noch ausführlicher an,
dieses Redefinieren, was es da für Möglichkeiten gibt.
Und demgegenüber steht das Redeklarieren. Auch das sind Begriffe,
die wir hier in der Veranstaltung so prägen.
Das ist das Ändern der Signatur einer Operation. Also ich
ändere tatsächlich dann die Schnittstelle. Wofür kann man sowas brauchen?
Ich habe da tatsächlich dann gleich ein paar Beispiele dafür.
Vielleicht kann ich die mal zuerst zeigen. Hier, sowas zum Beispiel
. Ich habe ein Interface Person und ich nehme mal an,
in einem bestimmten Kontext ist es einfach sinnvoll,
dass man Personen clonen kann in einer Software.
Dann ist es schlau, wenn die Klon-Operation etwas vom Typ Person liefert. Wenn ich dann eine andere,
einen anderen Typ habe, Student, der erweitert Personen,
dann soll der ja auch eine Operation Clone haben,
weil, wenn es ein Subtyp sein soll,
muss er alle Operationen seines Supertyps haben,
also muss er auch einen Clone haben.
Und vermutlich ist es sinnvoll, dass wir nicht einen Studentenklone,
dass dann da wieder ein Student rauskommt.
Und deswegen würde man dann denken,
dass dann auch hier der Ergebnistyp
Student sein sollte
Und das wäre dann eine Redeklaration. Das heißt,
da wird sozusagen die Signatur geändert. Okay,
in Java ist das nicht ganz richtig,
weil in Java der Ergebnistypen nicht zur Signatur gehört,
ich weiß, aber das ist jetzt hier sozusagen etwas breiterer Signaturbegriff.
Hier wird tatsächlich sozusagen die Schnittstelle geändert.
Also es ist ja schon für einen Klienten relevant,
ob er etwas kriegt vom Typ Person oder etwas kriegt vom Typ Student.
Das ist tatsächlich dieselbe Operation, es bleibt dieselbe Operation und
das ging in Java in der WM schon immer,
seit Java 1,5 geht das tatsächlich.
Ihr könnt tatsächlich den Ergebnistyp Co-Variante anpassen. Co-Variante heißt hier
in die Vererbungsrichtung. Also sozusagen mit der Vererbungsrichtung Person
Student kann ich auch den Ergebnistyp entsprechend mit anpassen.
Das ist zulässig. Was auch zulässig ist,
so um Anpassungen mal zu diskutieren, ist sowas hier.
Ich habe eine klasse Nahrung und dann habe ich eine Klasse Mensa-Essen.
Man munkelt das Mensa-Essen eine Art von Nahrung ist und
also haben wir so eine Subtyp-Beziehung hier.
Amensa-Essen ist Nahrung. So, jetzt habe ich hier eine Interface-Person
Und jetzt haben wir hier eine ganz komische Modellierung,
Interface-Person, die verzehrt Mensa essen.
Also die kriegt hier als Parameter Mensa essen.
Und wenn ich jetzt einen Interface-Student habe, dann ist es zulässig,
dass der dann hier als den Parameter-Typ anpasst in Nahrung.
Also nicht automatisch auch Mensa essen, sondern ändert in Nahrung.
Das geht in Java so nicht. Wenn das in Java
macht, führt das zu einem Overloading. Das heißt,
es sind dann zwei Operationen in der Schnittstelle von Student. In einer Sprache,
in der solche Anpassungen zulässig werden, es gibt solche Sprachen,
dann wäre das eine Co-Variante, eine Kontra-Variante Anpassung,
also gegen die Ererbungsrichtung, dann wäre das in Ordnung.
Das ist theoretisch interessant, aber praktisch völlig irrelevant.
Kein Mensch braucht das. Es gibt Sprachen,
die haben dann so, die waren ganz,
also die Objekten unter der Typtheorie hat das sozusagen mal rausgefunden,
dass das irgendwie bei Parametern gegen die Vererbungsrichtung Kontravariant,
bei den Ergebnistypen in Vererbungsrichtung, also Co-Variante, Anpassungen möglich sind.
Und dann haben einfach Leute Sprachen gebaut, in denen das dann möglich war.
Hat aber nie jemand benutzt, weil das braucht niemand.
Was einige Leute gerne hätten, wäre sowas hier.
Das ist das, was euch wahrscheinlich irgendwie Vernünftiger erscheint erst
mal sowas wie Nahrung und diabetische Nahrung.
Jetzt habe ich eine Person, Interface-Person und eine Person verzerrt Nahrung.
Kann also hier Nahrung aufnehmen als Parameter. Und dann haben
wir eine Spezialisierung, ein Diabetiker, der erweitert,
eine Person, also ist ein Subtyp von Person.
Und der verzerrt aber nur diabetische Nahrung. Das ist jetzt
genau wieder so ein Beispiel, das ist kein Subtyp.
Das ist kein Subtyp, weil dieser Diabetiker-Subtyp nicht an allen
Stellen einsetzbar ist, an denen eigentlich eine Person erwartet wird
Warum ist das so? Das kann man sich vorstellen.
Also erst mal ist es hier formal,
ist es eine Co-Variante, Anpassung des Parametertyps?
Das ist nicht in Ordnung. Und warum ist es nicht
in Ordnung? Ich kann mir so ein bisschen Benutzungsfall konstruieren.
Ich habe eine Variable vom Typ Person.
Und die verweist auf eine Diabetiker-Impel. Das ist ja,
weil Diabetiker irgendwie ein Subtyp ist, müsste das ja gehen.
Also kann ich hier eine Referenz auf einen Diabetiker einer Person zuweisen.
Dann erzeuge ich Nahrung, allgemeine Nahrung, keine Diabetikernahrung.
Und dann kann ich ja statisch im Quelltext schreiben, Person, verzehre Nahrung.
Weil das steht ja hier so vor mal da,
ich darf das ja aufrufen, ich darf ja bliebige Nahrung übergeben.
Weil aber zur Laufzeit dann Diabetiker hinter steckt,
kriegt er jetzt von mir irgendwie Nahrung, die er vielleicht nicht verträgt.
Also das ist hier so ein Punkt,
wo es dann kracht und in einer vernünftigen Programmiersprache führt das eben halt dazu,
dass das nicht zulässig ist.
Also solche Co-Varianten, Parameter-Typ-Anpassungen sind nicht zulässig. Das ist
beim Vertragsmodell so ähnlich. Beim Vertragsmodell ist es genauso,
ich habe einen Vertrag, in einer Klasse definiert.
Also ich habe eine Klasse, hier eine Operation R und habe hier Vorbedingungen und Nachbedingungen.
Letzte Woche hat Fred schon ein bisschen was darüber erzählt,
über solche Vorbedingungen und Nachbedingungen. Und jetzt haben wir den Fall,
wir haben eine Klasse D,
die erbt von C und redeklariert irgendwie hier diese Methode R. Also
ändert die irgendwie in ihrer Signatur und hat jetzt hier andere Vorbedingungen und auch andere Nachbedingungen.
So, und dann gibt es einfach als Gesetz für Vor- und Nachbedingungen,
es muss gelten, dass die Vorbedingungen von D schwächer ist als die von C.
Und die Nachbedingungen von denen darf gleich oder stärker sein.
Vor- und Nachbedingungen sind nichts, was formal erfasst sind.
Ihr wisst, dass es in Java,
das ist nur für uns nur Kommentare, die wir hinschreiben.
Das heißt, niemand prüft dieses, dieses Einhalten dieser Vorbedingungsgeschichten.
Ich habe da noch mal ein Beispiel dazu,
um das ein bisschen konkreter zu machen.
Wir haben also irgendwie einen Anbieter von Wurstücken.
Der hat hier eine Operation, liefere Burstück und der sagt,
die Burstücke bei mir kosten mindestens 500 Euro.
Die sind ziemlich teuer, aber wenn der Klient,
der mich aufruft, mir mindestens 500 Euro gibt,
dann liefere ich ein wunderbares Bohrstück mit einer sehr hohen,
mit einer sehr kleinen Toleranz.
Also die Toleranz ist irgendwie kleiner als 20. Millimeter,
Mikrometer, was immer. Also Vorbedingungen, Preis muss stimmen,
dann liefere ich auch was mit einer sehr hohen Genauigkeit.
So, jetzt gibt es einen Kunden, der fordert das an,
benutzt also diesen Anbieter und schließt dann natürlich statischen Vertrag mit diesem Anbieter.
Und jetzt sagt der Anbieter euch, baut baut diese Bohrstücke gar nicht selber.
Ich habe einen Zulieferer. Und der Zulieferer tritt quasi
wie ein Anbieter auf. Also maskiert sich als Anbieter
Und dann könnte jetzt der Zulieferer sagen, hm,
okay, ich bin ein ganz anspruchsvoller Zulieferer.
Erstmal bin ich teurer. Bei mir mindestens 600 Euro für
so ein Bohrstück. Und die Toleranz ist mir bei mir
auch schlechter. Also ich liefere schlechtere Qualität. Das würde
so nicht funktionieren. Das ist kein vernünftiger Subtyp.
Im Sinne der Vorbedingungen ist das einfach nicht okay.
Dieser Zulieferer dürfte nicht als Polymorph als Anbieter auftreten.
Also wenn wir das auf der Typebene jetzt mal auf der Objektebene uns angucken,
hier Anbieter A, konkreter Kunde A, Anbieter B,
dann Polymorph, macht das jetzt der Ziellieferer C und dann wird das hier so passieren.
Das heißt, so wird das nicht funktionieren.
Was funktioniert ist, Andersrum. Ja, ich kann die
Vorbedingungen abschwächen. Ich kann also sagen, ich liefere günstiger.
Also ich bin auch mit weniger als 500 Euro zufrieden.
Und ich liefere vielleicht sogar eine größere Genauigkeit.
Ich darf also Nachbedingungen, da darf ich,
da darf ich verschärfen, da darf ich sozusagen mehr liefern.
Aber bei Vorbedingungen darf ich in einem Subtyp nicht verschärfen.
Vorbedingungen dürfen schwächer werden, nach Bedingungen dürfen stärker werden.
Okay. Und das ist eben halt ein relativ komplexe Situation,
wenn man das in der Sprache garantieren will.
Deswegen gab es dann Sprachen, die dann so wie in Eifel,
die dann einfach diese Vorbedingungen mit einem oder verknüpft haben.
Eine Vorbedingung kann nur durch eine Oder-Verknüpfung erweitert werden.
Das ist etwas, was ihr euch dann,
sagen wir nochmal, ein bisschen durch den Kopf gehen lassen könnt.
Andere Sprachen haben das ähnlich gemacht, die auch das Vertragsmittel umsetzen.
In Varianten gehe ich jetzt auch mal drüber weg.
Sorry. Was ich eigentlich nochmal sagen wollte jetzt an
dieser Stelle ist Co und Contra-Varianz. Also diese Idee,
dass sich Dinge verschärfen in Vererbungsrichtung und auch Zusicherung,
denen liegt immer dasselbe Prinzip zugrunde
Ein Klient soll sich auf den statisch vereinbarten Vertrag verlassen können.
Und wenn aufgrund von Subtyp Polymorphie hinter diesem Ding,
was ich da benutze, ein anderer Subtyp verbirgt,
dann sollte der sich konform zu dem Vertrag des Supertyps verhalten.
Und so eine Co-Variante Parameter-Typ-Anpassung widerspricht dem genauso wie die Verschärfung einer Vorbedingung.
Das gerufene Exemplar akzeptiert in beiden Fällen nur einen kleineren Wertebereich.
Der Klient liefert etwas aus dem etwas größeren Wertebereich
und das kann zur Laufzeit zu viel anführen.
Das möchte man also nicht. Ein weiteres Beispiel ist Generalität
und Subtyping
Ha. Ja, ist eine Liste von Strings,
ein Subtyp einer Liste von Objekten.
Das ist mal eine Frage jetzt so zum späten Nachmittag nochmal,
so die müden Gemüter nochmal kurz strapazieren.
Wer ist der Meinung, dass eine Liste von Strings
ein Subtyp einer Liste von Objekten sein sollte? Also,
dass das, also so eine, ist eine Beziehung gibt,
ist eine Liste von Strings, eine Liste von Objekten.
Wer würde sagen, das stimmt? Ja,
wer ist der Meinung, dass das nicht so sein sollte?
Ja, das ist zwar wenig Meinung, okay.
Also, nehmen wir mal sowas an hier,
Liste von Objekten
Also eine Variable ist jetzt hier eine Liste, eine Liste von Objekten.
Und ich habe eine Liste von Strings. Und ich habe jetzt hier
eine Arraylist von String, die er als Implementation dahinter steckt.
So, jetzt kann ich ja meine Variable Objektliste hier,
der kann ich jetzt die Referenz auf die Stringliste zuweisen.
Die Frage ist, ob das zulässig ist. Wenn ich das machen könnte,
dann könnte ich jetzt irgendwo weiter unten sagen,
Objektliste at New Object, dann würde ich sozusagen in die Arraylist,
in die ja nur Strings rein dürfen,
in diese Liste von Strings,
würde ich ja jetzt hier
ein Objekt einfügen
Und dann würde ich, wenn ich hier jetzt das aufrufe,
keinen String rauskriegen, sondern ein allgemeines Objekt.
Das wäre sehr, sehr komisch. Und deswegen,
das ist eine Co-Variante, Typ-Parameter-Anpassung. Und auch die ist
genauso wieder verboten. Das heißt also, auch das ist
statisch nicht erlaubt. Das gibt also einen entsprechenden Übersetzungsfehler.
Das würde also gar nicht erst passieren hier. Co-Variante,
Anpassung eines Typ-Parameters bedeutet meist auch bei der Operation Ed,
eine Co-Variante, Parameter-Typ-Anpassung.
Und die habe ich ja gerade erzählt,
ist nicht typsicher,
nicht statisch typsicher.
Dann machen wir noch mal ein bisschen Storesi. Mal gucken,
ob das hier funktioniert. Ich habe hier eine Tiny Euro 11.
Für diejenigen, die die mal eben eintippen wollen.
Ich habe auch noch mal den Barcode dabei.
Ich hoffe mal, dass das jetzt funktioniert. Abstimmung,
SE2, das ist hier die ID und wir nehmen SE2,
Vorlesung 3 Laden. Könnt ihr diese Frage jetzt auch sehen,
wenn ihr jetzt, wer hat jetzt das Storesi da,
hat jemand die Webseite schon?
Ich zeige mal
den Barcode
Hier ist der Barcode. Kommt da was Vernünftiges bei raus?
Arztglasfeind, wo er jetzt sagt. Ja. Hier ist die URL,
tiny eurol.com, das kann man sich leicht merken,
und dann CRS, Classroom-Response-System, minus AS
2013. So, und jetzt zeige ich noch mal den Barcode.
Sorry. Ja, wir müssen uns da jetzt
wieder reingrooven in Shoresi
Kann da irgendjemand was sehen jetzt? Sieht jemand die Webseite
? Und da ist auch die richtige Frage. Wow,
es hat geklappt. Ein Wahnsinn. Und jetzt haben wir
hier auch, das läuft ja schon die Abstimmung,
wie viele Interfaces kann Interfaces in Drava erweitern?
Habe ich vorhin erzählt. 17 Stimmen, Wahnsinn. 22,
oh, 33. 37. Wahnsinn, Wahnsinn, Wahnsinn.
38. So viele Leute hier
Ja, das hat man schon. So,
bei 50 hören wir auf, damit wir heute fertig werden.
Ja, Gott, wir gucken mal auf die Verteilung.
Ja, also zwei Drittel sind der Meinung,
es sind beliebig viele und das ist auch die richtige Antwort.
Okay, das war auch eine Frage zum Warmwerden hier für Storesi.
Dann habe ich hier ein weiteres Projekt. Ja, hier,
ich habe hier vier Klassen, die alle miteinander in Beziehung stehen,
ein Interface, ein Interface, Interface A,
definiert eine Methode do something mit einem Parameter Y.
Dann habe ich ein Interface B, das Extense A,
also Erbt von A, ist ein Subtyp von A,
da gibt es die Methode Dumore mit einem I als Parameter.
Dann haben wir hier Interface C, Extense auch A
mit einem Dumore, mit einem Bulien als Parameter.
Und dann haben wir zum Schluss hier unten das Interface D,
das jetzt von B und C erbt, weil das geht ja,
multiples Subtyping ist ja für Interfaces zugelassen in Java.
Und definiert hier eine Methode Dumore mit einem I als Parameter.
So, meine Frage dazu lautet, wie viele Operationen definiert
der Typ D über die Typ Object natürlich, also nicht jetzt
Equals Hashcode und so weiter, sondern wie viele Operationen definiert
der Typ D, über dem Typ Object definierten Operationen hinaus.
Zwei, drei, vier oder fünf. Und ich wende
das nochmal aus, ja, also zwei, drei,
vier oder fünf und dann jetzt hier nochmal Situation.
Hier sind die vier Typen mit ihren Operationen und wie viele
definiert jetzt der Typ D da unten?
Zwei, drei, vier oder fünf. Ist die neue
Frage nicht sichtbar jetzt? Also diese Frage sollte jetzt eigentlich
da irgendwo auftauchen. Wie viele Operationen definiert der Typ D
? Eventuell neu laden im Browser
So, 25 Stimmen, möchten euch immer eine Stimme abgeben.
27, so entscheidet euch jetzt, die Vorlesungszeit ist rum.
36 Stimmen, ja, 43 Stimmen, ja,
komm, 50 Stimmen noch, gebt mir eure Stimme.
So. Ah, ja, geschafft. Gut. Ja,
deutlich schwieriger Gemengelage, man weiß nicht mehr so genau,
was hier jetzt richtig und falsch ist.
Und also hier sind jetzt tatsächlich dann doch wieder die Mehrheit,
hat das Richtige, das sind also hier die,
es sind drei,
es sind drei.
Warum sind es drei
Ich kann das noch mal hier zeigen, das Beispiel.
Too something here, eine, Dumore, die zweite,
Dumore, die dritte. Und hier ist ein Dumore,
aber der wiederholt eigentlich die Signatur von da.
Und das ist ja in, da war zulässig,
dass man die Signatur einfach wiederholt.
Das haben wir bei Set ja auch gesehen.
Das heißt, das ist nicht eine weitere Operation,
sondern es ist einfach die gleiche nochmal.
Also sind es insgesamt drei Operationen.
Danke für eure Geduld heute.