Ich glaube, das ist ein anderes Phänomen. Okay, ich rede jetzt schneller.
Das ist voll... Wie oft sie das schon erlebt haben,
sie schreiben ein Stück Code, das tut seine Aufgabe,
jetzt haben sie eine andere Aufgabe und denken,
ich habe schon mal so ein Stück Code geschrieben,
das passt schon so ungefähr so 80, 90 Prozent.
So ein bisschen was ist anders, was ich jetzt mache,
ist copy-paste, dann passe ich das an.
Und dann hat man halt so ein bisschen doppelten Code,
sogenannten Duplicated Code ein bisschen was anderes und alles ist gut.
Haben Sie effizient Ihre, die zweite Aufgabe erledigt? So,
das machen Sie ein paar Mal. Und dann stellen Sie fest,
oh, der Teil, in dem ich jetzt was,
den ich kopiert habe, da ist was faul.
Irgendwas, was Sie erst später bemerkt haben.
Und jetzt gehen Sie auf die Suche nach diesem duplizierten Code-Anteil.
Das ist so ein, in der Praxis extrem gängiges Verfahren,
das schafft Arbeit, füllt Arbeitszeit nicht immer nur zum Spaß
Und dann kommt jemand wie ich in ihr Unternehmen
und schmeißt also sowas zum Beispiel wie den Software-Tomographen an.
Also so ein Ding,
mit dem man Code analysieren kann und stellt fest,
so viele Code-Duplikate sind vorhanden und ich attestiere ihnen eine hohe Code-Erosion.
Schlechte Code-Qualität, das hat ihr Chef dann nicht gerne.
Sie auch nicht. Und heute werde ich ihnen mit dem Konzept der Vererbung von Code,
werde ich Ihnen ein Mittel vorstellen,
mit dem Sie in der Lage sind,
also diesen Zustand zu vermeiden
Kann ich etwas wiederverwenden, von dem ich glaube,
dass es auch an anderen Stellen nützlich sein könnte.
Sondern das gucken wir uns in unter dem Stichwort der sogenannten Implementationsvererbung an.
Und die Implementationsvererbung, so wie wir sie hier vorstellen werden,
also so wie ich sie Ihnen vorstelle, wir sie uns anschauen werden,
die beinhaltet drei unterschiedliche Aspekte, drei verschiedene Ausprägungen.
Einmal das Erben zum Anpassen. Das ist das, wovon
ich jetzt gerade gesprochen habe. Und das Vermeiden von Redund
und zum Vermeiden von Redundanzen, denn das wollen wir auch nicht.
Es gibt so Es gibt so ein Prinzip,
das heißt Once and Only Once, das sogenannte Vora,
das White Once and Only Once Prinzip.
Das wollen wir hier in diesem Zusammenhang kennen, kennenlernen.
Und die dritte Art von Implementationsvererbung,
die ich hier auch noch beschreiben werde, ist die sogenannte,
ist das Erben zum Vervollständigen von Code.
Das ist so, wir werden im Rahmen der Vorlesung
heute auch noch ein weiteres Geheimnis lüften, etwas,
wovon sie sich möglicherweise schon mal gefragt haben,
wieso funktioniert das
Da denke ich, dem werden wir heute auch noch auf die Schliche kommen.
So, jetzt schauen wir uns erstmal das da an.
Und hinzufügen, gegeben sei, jetzt gucken sie sich diese Klassenhierarchie an. Sagen wir mal,
sie haben sowas wie eine Klasse Konto geschrieben und diese Klasse Konto hat die Schnittstelle,
also genau eine Operation, nämlich Gibsaldo und die ist auch implementiert.
Diese Operation und das,
was sie jetzt ausdrücken können mittels dieses Sprachkonstrukts, Vererbung ist,
dass sie so eine Klasse Sparbuch schreiben können und von dieser Sprache klassische Sparbuch,
sagen sie, sie ist ein Konto.
Also das ist sie so von der Sprechweise bedeutet,
ist ein. Nun, ein Sparbuch hat alles,
was ein Konto hat und möglicherweise noch etwas mehr.
Und dieses möglicherweise noch etwas mehr ist im Falle des Sparbuches
nicht nur ein möglicherweise, sondern es ist tatsächlich so,
dass also diese Klasse Sparbuch zusätzlich zu den Operationen,
die das Konto schon implementiert, auch noch eine Operation,
wie heißt sie genau, berechnet Zinsen hinzufügt.
Das heißt,
wenn sie jetzt also dieses Mittel noch nicht zur Verfügung hätten,
von dem ich jetzt gerade gesprochen habe, müssten sie ja sagen,
ah gut,
das Konto,
das hat schon sowas
Wie das, was ich haben will. Ich kopiere mir
mal den Code und jetzt schreibe ich eine klasse Sparbuch,
da kopiere ich diesen Code erst mal rein und dann implementiere ich zusätzlich noch die Operation berechnet Zinsen.
Diese Vorgehensweise ist hiermit Geschichte. Sie lernen nämlich jetzt dieses
Neue, für Sie neue Java-Schlüsselwort kennen, nämlich Extense.
Mit der Schlüsselwort Extense so in der Art und Weise,
wie das hier steht, können Sie diesen Gedanken ausdrücken,
den ich gerade so etwas erläutert habe,
nämlich Ich möchte eine Klasse in diesem Fall Sparbuch schreiben und die soll alles haben,
was die Klasse hat, von der ich erbe,
in diesem Fall Konto.
Und dann mache ich mir noch Gedanken darüber, was zusätzlich
in dieser Klasse vorkommen soll. Wir haben im Kreis derjenigen,
die hier jetzt ihnen auf dieser Seite des Auditoriums gegenüberstehen,
schon mal darüber gesprochen. Och, das mit dem Kundekonto Sparbuch Klassen,
so immer das an diesen Beispielen klarzumachen,
die intellektuelle Hürde, um es mal milder auszudrücken,
ist eher gering.
Um nicht zu sagen,
das ist stinkt langweilig
Es gibt ein pädagogisches Argument dafür. Wir möchten Ihnen hier
objektorientierte Programmierung beibringen. Und die eine Fraktion sagt halt,
das ist schon schwierig genug. Da brauche ich dem Anwendungsbeispiel,
das braucht nicht selber noch eine Hürde sein,
sodass sie so mit zwei Fronten zu kämpfen haben.
Und ich bin ein Freund dieser Herangehensweise. Und Fred,
der jetzt gerade gekommen ist,
der gibt ihm halt noch gerne zusätzlich einen mit,
indem er Ihnen die Vorzüge der Postfix-Notation versucht einzureden.
Hoffentlich erfolgreich. Da müssen Sie nämlich auch nochmal drüber nachdenken.
Also mir geht es jedenfalls so
Das heißt, sie kriegen hier in dieser Vorlesung die Fried
und ich gemeinsam halten, sozusagen best of both worlds.
Zurück zur Vererbung. Die Klasse Sparbuch. Erbt also alle
Operationen und Felder, sprich also alle Exemplarvariablen der Klasse Konto.
Und dadurch, dass sie das hier ausgedrückt hat,
Klaas Sparbuch, Extents-Konto, alles, was hier drin ist,
steht jetzt auch hier zur Verfügung.
Und es kann noch etwas hinzugefügt werden. Und das
passiert auch. So, wie wird das jetzt verwendet?
Nun, schauen Sie sich dieses Code Snippet
einfach an
Ich deklariere mir eine Variable von der Klasse Sparbuch.
Ich bin da ein Objekt der Klasse, Sparbuch da dran.
Und dann kann ich an diesem Sparbuch alles aufrufen,
also an diesem Objekt, das als Sparbuch deklariert ist.
Ich kann die bezeichnen, kann ich die Operationen aufrufen,
die an dem Sparbuch, an der Klasse Sparbuch selbst geschrieben worden sind,
plus alle die Operationen, die schon an der,
in der Klasse Konto implementiert und exportiert worden sind.
Das ist jetzt ein weiteres Beispiel dafür für Polymorphie
an der Stelle
Das werden wir noch sehen, nämlich so die etwas lustigeren,
naja, spannenderen Fragestellungen, was passiert eigentlich,
wenn an einen Bezeichner, der statisch vom Typ Konto deklariert worden ist,
ein Objekt der Klasse Sparbuch gebunden wird?
Was kann man dann aufrufen? Und was lässt einem der Compiler,
beziehungsweise ja, der Compiler durchgehen als an Aufrufen, die man da hinschreibt?
Zunächst mal halten wir jetzt an der Stelle fest,
sie haben also mit dieser Extense-Beziehung, als die Möglichkeit auszudrücken,
dass ein Konzept genauso wie ein anderes Konzept ist,
plus möglicherweise etwas mehr an Operationen zur Verfügung stellt.
Das ist ja schon mal ein Mittel, mit dem wir umgehen können,
um diese Art von Copy-Paste-Vererbung, wie das auch genannt wird, zu vermeiden.
Also Redundanten, Code zu schreiben. Wir können darüber hinaus,
wenn sie sich noch an die letzte Vorlesung erinnern,
wenn sie so in dem Anwendungsbereich,
in dem ihre Programme geschrieben werden sollen oder für den ihre Programme geschrieben werden sollen,
wenn sie in diesem Anwendungsbereich so Begriffshierarchien entdecken,
haben sie jetzt ein Mittel, mit dem sie diese Begriffshierarchien auch auf Code-Ebene,
die Umgangsformen ausdrücken können.
So, jetzt kommt noch ein weiteres Schlüsselwort
Wir haben Ihnen bisher zwei sogenannte Modifikatoren nahegelegt. Ein Modifikator Public,
alles ist sichtbar, das war der Modifikator, den wir verwendet haben,
um die Operationen öffentlich sichtbar zu machen. Jedenfalls die,
die sichtbar sein sollen und den Modifikator Private,
haben wir gesagt, so, das muss für alle Exemplarvariablen gelten,
weil wir wollen nicht das Exemplarvariablen nach außen sichtbar sind,
diese Art von Implementationsdetails, sollen vor der Klientenschnittstelle verborgen werden.
Mit dem Einführen der Vererbungsbeziehung kommt jetzt ein zusätzlicher Modifikator dazu,
der ist sozusagen nicht so zu sagen,
der ist zwischen Private und Public angesiedelt, der ist,
das ist der Modifikator protected.
Protected bedeutet an der Use-Schnittstelle, also die Schnittstelle,
die sie bis jetzt kennengelernt haben, ändert sich nicht.
Als Protected, deklarierte Variablen, Exemplarvariablen und Methoden
sind für die Klientenstelle, Schnittstelle nicht sichtbar.
Für die erbende Schnittstelle sind protected mit Protected deklarierte Variablen und Methoden sichtbar.
Wir werden uns noch Anwendungsfälle anschauen, wo genau diese Unterscheidung
auch sinnvoll ist. Sie sehen das hier In diesem Beispiel,
dass also die als Protected deklarierte Variable,
auf die kann innerhalb der Implementation der Unterklasse zugegriffen werden.
Wäre die als Private deklariert, könnte ich das,
obwohl sie in dieser Klasse vorhanden ist,
über den Vererbungsmechanismus, könnte darauf nicht zugegriffen werden.
So, jetzt kommt das Geheimnis, von dem ich vorhin schon gesprach,
das ist jetzt gelüftet wird. Sie haben sich möglicherweise gefragt,
warum kann an den Klassen, die ich schreibe,
obwohl ich doch nie etwas damit zu tun hatte,
so eine Operation wie Hash Code, Equals oder Two String aufgerufen werden.
Und die des Rätsels Lösung besteht darin, dass jede Klasse,
die sie in Java schreiben, hat implizit eine Oberklasse.
Und das ist die Klasse Object. Und die stellt genau diese Operationen
zur Verfügung. Das heißt, die sind immer schon da.
Sie brauchen nicht zu schreiben, klasse Konto, Xtense Object.
Ich weiß gar nicht, ob das geht.
Fred. Ja. Wie bitte? Es ist dasselbe.
Ich würde nur, wenn du das einfach nicht entscheidest.
Ja, ja
Aha, das geht also. Gut. Interessant.
Diskutieren Sie das mit Fred, wenn Sie Lust haben.
Ja, also das brauche ich nicht hinschreiben,
sondern das ist immer schon da, also sozusagen eine verkürzte Schreibweise.
Und das, was ich tun kann,
wenn ich weiß, welche Operationen ich von Object erbe,
ist, ich kann diese Operationen redefinieren.
Das ist jetzt eine neue Form oder eine neue Möglichkeit,
über die wir noch nicht gesprochen haben. Bis jetzt habe ich darüber gesprochen,
dass man durch Also das Erben, man bekommt alles,
was die Oberklasse an öffentlichen oder protected modifizierten Schnittstellen zur Verfügung gestellt hat, Schnittstellen und Exemplarvariablen.
Und dann kann man noch etwas hinzufügen.
Jetzt kommt eine weitere Form. Ein weiterer Mechanismus zum Tragen,
der jetzt über so die implizite Oberklasse Object ihnen schon bekannt ist,
aber den können sie auch für ihre eigenen Klassenhierarchien,
können sie den verwenden.
Jetzt nehmen wir mal wieder
unser Standardbeispiel
Nehmen an, dass es schon in der Klasse Konto eine
Operation mache, Jahresabschluss gibt. Also irgendwas ist da implementiert,
was halt relevant und richtig ist für die Klasse Konto.
So, jetzt kommen Sie und sagen, naja gut,
mein Sparbuch ist ein Konto, ich erbe dieses ganze Zeug von meiner Oberklasse,
aber die Operation mache Jahre Arbeitsabschluss, Jahresabschluss soll einen anderen,
soll eine andere Implementation haben.
Und dann machen Sie das einfach
Und jetzt wird es, das ist das Eingangstor potenziell zum Himmel und zur Hölle.
Das ist jetzt ihre Entscheidung. Ich gebe Ihnen nur
das Mittel in die Hand, was Sie damit machen.
Das ist Ihre Angelegenheit. Warum das? Also so gefährlich ist,
können Sie sich mal vielleicht an dem Beispiel Gibsaldo versuchen klarzumachen.
Also hier wird meinetwegen wirklich der Kontostand ausgegeben. Jetzt gibt es
einen jungen Menschen, der sagte so noch im Sparbuch,
ich will die Operation Gibsaldo redefinieren und das,
was ich da zurückgebe, ist immer Null.
Oder minus eins. Und das ist jetzt ein Effekt,
den sie erzielen können, der von, also der möglicherweise von denjenigen,
die die Klasse konto implementiert haben, nicht gewollt gewesen ist.
Das kann jetzt also ihre Programme können durch das Wunder
der Polymorphenbindung jetzt Effekte, die unbeabsichtigt gewesen sind,
an Stellen zeigen,
die die
jahrelang nicht angefasst worden sind
Das will man einerseits haben, weil Erweiterbarkeit von Softwaresystemen,
die genau durch dieses Mittel erreicht werden kann,
Erweiterbarkeit ist ein Qualitätskriterium für objektorientierte Programme.
Der Seiteneffekt besteht darin oder so, das Tor zur Hölle besteht darin,
dass sie auch Fehler an Stellen einführen, Fehler an Stellen auftreten können,
die sie seit Jahren sie oder andere seit Jahren nicht angefasst haben.
Wir haben ein Mittel kennengelernt, dass die Semantik unserer Operationen
absichert gegenüber solchen Veränderungen. Und das ist das Vertragsmodell.
Und wir werden uns möglicherweise nicht im Rahmen dieser Lehrveranstaltung,
sondern erst in einem späteren Semester und damit beschäftigen,
wie das Vertragsmodell eingesetzt werden kann, um solche Effekte zu vermeiden.
Und das Vertragsmodell, Sie erinnern sich, ist ja dazu da,
dass es Operationen nicht nur über Ihren Namen, also deren Bedeutung,
nicht nur über den Namen und Parameter so erraten werden muss,
sondern dass man auch semantische Operationen, semantische Randbedingungen hinzufügen kann.
Und das Verhältnis zwischen Vertragsmodell und Vererbung
ist also auf jeden Fall noch eines weiteren Blickes wert.
Zurück zu dem schlichten Mechanismus
Sie können Operationen, die schon eine Implementation in der Oberklasse
haben. Und das ist ja der Stand der Kunst,
über den wir im Moment sprechen. Die können sie redefinieren,
sodass diese Operationen zwar noch denselben Namen haben und also dieselbe
Anzahl und Stelligkeit von Parametern, aber über eine andere Implementation verfügen.
Ein kleines Beispiel, das ist also diese Operation,
mache ich Jahresabschluss im Konto und die hat ganz schlicht,
also diesen, das ist diese Code-Zeile und die Reinterpretation im Sparbuch,
da mussten halt jetzt noch Zinsen zusätzlich berechnet werden.
So, wenn sie jetzt gerade so sich nochmal rekapitulieren,
worüber ich gerade gesprochen habe, Code-Duplikate sollen vermieden werden durch den Einsatz der Vererbungsbeziehung.
Und was mache ich hier? Also jetzt erst erzählt er uns,
wir sollen Code, sollen wir nicht duplizieren,
setzt die Vererbungsbeziehung ein und jetzt schreibt er hier,
wiederholt er hier, kopiert er den Code,
also den er oben in Machejahresabschluss in dieser Operation hingeschrieben hat.
Also wenn das kein Code-Duplikat ist, dann weiß ich es
auch nicht. Ja, da haben sie verdammt recht
Aber das ist auch noch nicht das Ende der Veranstaltung.
Es ist erst 14.36 Uhr, noch ein weiteren Mechanismus kennenlernen
und das ist der da. Wenn Sie diese Operation,
die Sie aus der Oberklasse geerbt haben,
wenn Sie die nicht komplett überschreiben wollen,
sondern wenn Sie darauf Bezug nehmen wollen, gibt Java Ihnen diese Möglichkeit,
indem Sie die Operation der direkten Oberklasse aufrufen.
Das kann man kenntlich machen, indem man dieses Schlüsselwort super,
das ist das Kurz für superklasse, also Oberklasse,
toll ist, sondern dass die einfach die Oberklasse ist,
dass man dieses Schlüsselwort super dazu schreibt.
Und das bedeutet, dass die Implementation genommen wird, die in der direkten Oberklasse vorhanden ist.
So, und damit können wir dieses Problem lösen,
dass ich jetzt gerade liebevoll aufgebaut habe. Ich sage also,
in der Implementation von Mache-Jahresabschluss, in der Unterklasse Sparbuch,
sage ich, super Punkt, mache ich Jahresabschluss und das bedeutet,
es wird also in der Klassenhierarchie wird die Oberklasse verwendet,
um diesen Code auszuführen und dann mache ich das,
was ich sowieso machen wollte, nämlich hier noch diese komischen Zinsen zu berechnen.
Man kann immer nur,
das sollte man auch noch kurz bemerken, sollte nicht auf,
man kann in Java-Programmen immer nur auf die direkte Oberklasse zugreifen,
nicht auf, also noch möglicherweise weitere Oberklassen.
Das reicht aber auch aus, weil schließlich kann die Oberklasse,
wenn sie das für sinnvoll hält,
ja auch nochmal auf ihre Oberklasse zurückgreifen.
Es verringert einfach die Komplexität dessen,
was sie anrichten können
So, jetzt können wir,
jetzt habe ich so diese zwei, ein, zwei Mechanismen,
also erweitern und redefinieren, habe ich jetzt vorgestellt.
Jetzt kommen so ein paar Fragen. Keine Angst, nicht
an Sie. Jetzt stellen Sie sich möglicherweise dich fragen,
was passiert jetzt eigentlich? Jetzt ist ja,
jetzt kann ich diese ganzen Effekte erzielen und ich brauche auch
keinen duplizierten Code mehr schreiben, beziehungsweise kopieren und wieder irgendwo anders hinpasten.
Jetzt können wir uns ein paar Gedanken darüber machen,
was ist jetzt eigentlich möglich, gewollt oder ungewollt.
So, zunächst mal müssen wir verstehen,
also was jetzt eigentlich alles passiert.
Nehmen wir mal an
Es gibt also diese Klasse namens Oberklasse,
die hat eine Operation 1 und eine Operation 2 und
in der Operation 1 wird die Operation 2 aufgerufen.
Ja, von mir aus. Es ist halt so.
Das macht man ja häufiger, oder?
Dass man also nicht nur Operationen aus anderen Klassen ruft,
sondern dass eine Klasse auch eine Operation ruft,
die in ihr selbst implementiert worden ist.
Warum nicht? Jetzt nehmen wir im folgenden Fall dazu,
in der Klasse Unterklasse wird die Operation 2 redefiniert
Und jetzt ist die spannende Frage, ist,
welche Operation wird denn jetzt eigentlich ausgeführt? Und das ist,
wir haben gestern doch gelacht, als wir Fried und ich,
als wir uns diesen Satz einmal laut vorgelesen haben.
Und ich hoffe, das findet jetzt auch ihren Humor,
wenn in einer Unterklasse eine Operation redefiniert wird, also die Operation 2.
Das ist nicht der Effekt, wenn Operation 2 aufgerufen wird.
Also der Tag wird kommen, an dem ich
meinen Schreibtisch aufräume
Das behaupte ich aber auch schon seit ich, seit ich solche Computer verwende.
Darf ich zurückkehren zu meinem Satz, wenn in einer Unterklasse
eine Operation redefiniert wird. Also hier die Operation 2,
die in der Oberklasse durch eine andere Methode aufgerufen wird,
also in unserem Fall die Operation 1.
Dann wird in der Unterklasse die neue Implementation dieser Operation aufgerufen.
Ich gestehe, dass ich diesen Satz,
also ich kann schon kaum noch vorlesen,
aber verstehen tue ich den auch,
also nur mit wenig Alkohol
im Blut
So, ich versuche was, mir das aber am Beispiel mal an einem,
also etwas so ganz pragmatisch, so mir das klarzumachen, was da passiert.
Nehmen wir mal an, sie, also sie sind so drauf,
wie ich das jetzt gerade nahe gelegt habe,
nämlich es gibt diese Unterklasse und dann Dreh definieren sie die Operation 2 und sie sehen ja in ihrer Unterklasse,
sehen sie ja, ach guck mal,
die Operation 1, ruft die Operation 2 auf.
Ja, bitteschön. Dann ist es ja absolut in ihrem Sinne,
dass wenn sie eine Variable von der Klasse, Unterklasse deklarieren,
dass die von redefinierte Operationen aufgerufen wird und nicht die Operation von der Klasse,
von der sie geerbt haben.
Das ist doch nachvollziehbar, oder? Und
die beruhigende Nachricht ist, so ist es dann auch.
So ist es in Java implementiert. Es gibt andere Sprachen,
andere Programmiersprachen, in denen das anders implementiert ist.
Aber das ist eine Art von Interpretation,
die sie in einem Seminar oder in einer Veranstaltung,
wo es also ganz spezifisch um die Implementation von Programmiersprachen gibt,
so dieser Fragestellung, können Sie da besser nachgehen?
Das, was ich jetzt gerade erläutert habe,
ist hier nochmal an dem Beispiel Konto Sparbuch, ist das also erläutert.
Wenn es also so eine Operation Drucke, Jahresbericht in der Oberklasse
gibt und die ruft eine Operation, mache Jahresabschluss auf.
Das ist ja in der Klasse Konto, soweit wie wir
das Beispiel entwickelt haben, ist es ja Teil der Implementation,
Teil der Schnittstelle, der Klasse Konto.
Und ich rufe in einem Sparbuch Drucke, Jahresbericht auf,
dann soll natürlich die hier überschriebene Operation mache, Jahresabschluss aufgerufen werden.
Alles andere würde man sich ja fragen, wie komme ich jemals?
Also dann, wenn ich einen Jahresbericht mache,
eigentlich an den Jahresabschluss des Sparbuchs dran
Das ginge, aber das wäre anders, schwieriger. So,
das Konzept mit dem, also sie sich das vorstellen können,
das in Java-Implement oder das, hier steht so das,
was da vorher stand, war das mentale Modell,
das ist mir ein bisschen, weiß ich nicht so genau,
was das bedeutet, also sage ich lieber die zwei Modellvorstellungen anhand derer Sichtmann,
das erklären kann, was hier passiert und das,
was in Java implementiert ist, ist das sogenannte Jojo-Modell,
also dieses Spielgerät,
das da immer runter und rauf geht.
Die Selbstaufrufe, also Operationen in der Klasse,
in die ich geschrieben habe, die werden auch,
also wie das hier steht, dynamisch gebunden. Und die Idee ist,
dass Laufzeitsystem guckt immer zuerst in der Klasse nach,
also in der ich mich gerade befinde, also ich,
das Laufzeitsystem, um herauszukriegen, ob es dort eine aktuelle Implementation für diese Operation gibt.
Und erst in dem Moment, wo das nicht der Fall ist,
Geht es also in der Klassenhierarchie nach oben und guckt,
ob es da eine Implementation gibt.
Das heißt, wenn Sie eine Operation redefinieren in einer Unterklasse,
können Sie auch sicher sein, dass das die Operation ist,
die gerufen wird zur Laufzeit.
Wenn Sie das nicht tun, dann wird halt im Klassenbaum nach oben gegangen.
Das geht immer. Das ist ja gerade der Vorteil von
statisch getypten Programmiersprachen. Sie können zur Laufzeit nicht Fehlermeldung der Art,
method not found, method not anders tut,
was ihnen dynamisch getypte Programmiersprachen gerne als Fehlermeldung geben
Diese Art von Fehler kann man praktisch nicht herstellen.
Es gibt also theoretisch Möglichkeiten, das zu tun,
aber im Alltagsgebrauch findet das nicht statt. So,
jetzt kommen wir noch zu einem Element, das so einem konzeptionellen,
einer konzeptionellen Idee, die sich jetzt quasi nahtlos aus dem anschließt,
worüber wir ich mit Ihnen bis jetzt schon gesprochen habe.
Jetzt haben wir also die Idee, na gut,
ich kann alle diese Operationen hier, ist man immer aus da.
Alle diese Operationen, über die ich schon hinlänglich gesprochen habe,
die kann ich also implementieren. Was ich gerne ausdrücken möchte,
ist, ich möchte, dass alle Klassen,
die jemals von einer Klasse Konto erben,
dass die auch die Operation ist Betrag gedeckt, implementieren.
Beziehungsweise sagen wir mal, etwas vorsichtiger zur Verfügung stellen.
Was ich allerdings noch nicht tun kann,
während ich die Operation, während ich die Klasse Konto schreibe,
ich weiß noch nicht,
wie ich
die implementieren soll
Weil so in meiner Modellvorstellung gehe ich mal davon aus,
es sollte diese Operation geben, aber die wird für Sparbuch,
für ein Sparbuch, wird die anders aussehen als,
sagen wir mal, für ein, wie heißt das, irgendein Kinderkonto.
Oder von mir aus auch für ein Firmenkonto. Das heißt,
ich kann an der Stelle, wo ich diesen Code ablegen möchte,
wo ich den implementieren möchte, kann ich diese Entscheidung noch nicht treffen?
Kann auch sein, dass ich die noch nicht treffen will,
eben weil diese Implementationen jeweils so unterschiedlich sind. Da das ein häufiger Fall ist,
Also diese, dass man eine Entscheidung noch nicht treffen kann über eine Implementation oder auch noch nicht treffen will,
gibt es die Möglichkeit,
eine Methode als abstrakt zu deklarieren und abstrakt bedeutet,
nichts anderes als, ich weiß noch nicht,
was ich da implementieren möchte, wenn jemand von mir erbt und möchte,
dass von dieser Klasse ein Objekt erzeugt werden können soll,
dann soll er das tun.
Also dann soll er die Implementation vornehmen,
dann ist auch, ist es auch möglich,
ein Objekt von der Klasse
zu erzeugen
In dem Moment, in dem ich also die Entscheidung treffe,
ich will zwar, dass diese Operation zu meiner Schnittstelle gehört,
ich weiß aber nicht, wie die implementiert werden sollen,
sage ich einfach, ich deklariere diese Methode als Abstract.
Also Abstract nicht konkret. Und das bedeutet, das hat als Konsequenz,
dass ich von dieser Klasse kein Objekt mehr erzeugen werden kann.
Und was soll's? Ich wollte von der Klasse Konto auch nie ein Objekt erzeugen,
weil es gibt gar nicht das Konto als solches,
sondern es gibt dieses Konto nur als Abstraktion von Sparbüchern,
Mäusekonto, Firmenkonto, was auch immer an konkreten Konten erzeugt werden können soll.
So, das ist das Mittel der abstrakten Methoden und damit
auch der abstrakten Klassen. Ups, Entschuldigung. So,
was sehen Sie jetzt hier noch mal am Beispiel?
Also wenn ich das in der, ich sage jetzt also in der,
ich sage in der Oberklasse, es gibt diese abstrakte Methode.
Das bedeutet, ich kann keine Objekte der Klasse Konto mehr erzeugen.
Ich sage in der Klasse Sparbuch, die ist nicht abstrakt.
Das können Sie auch mal probieren, wenn Sie jetzt sagen,
ich ehre die Klasse Eine Klasse erbt von einer nicht abstrakten Klasse,
ohne dass sie selber von sich sagt, also ich bin eine abstrakte Klasse,
dann wird ihnen der Compiler das nicht durchgehen lassen.
Und er wird sagen, also Leute,
entweder ihr implementiert jetzt das,
was in der Oberklasse als abstrakt deklariert worden ist,
oder ihr erklärt euch selber zur abstrakten Klasse,
aber eins und beiden muss man sich entscheiden.
Es wird auf jeden Fall nicht der Fall sein,
es gibt eine konkrete Klasse mit einer abstrakten Methode, das geht nicht.
Das ist jetzt, wenn Sie sich erinnern,
Sie kennen ja so, wir eröffnen ja jetzt hier Möglichkeiten.
Sie kennen jetzt schon drei Modifikatoren,
Private,
Protected,
Ich kenne jetzt auch drei Gestaltungsmittel für Konzepte, die sie ausdrücken wollen.
Sie kennen Interfaces, ausschließlich Schnittstelle. Sie kennen Klassen,
das ist also Implementation, Objekte können erzeugt werden und
dazwischen kennen sie jetzt das Mittel der abstrakten Klassen.
Also nach dem Motto, ich kann schon so ein bisschen
was über die Implementation sagen, aber nicht alles,
kann beziehungsweise will noch nicht alles über diese Implementation sagen.
Und das eröffnet schon einen ziemlich breiten Entscheidungsspielraum,
Gestaltungsspielraum für die Konstruktion größerer Systeme
So, ich habe jetzt darüber gesprochen,
dass das so die, welche Eigenschaften abstrakte Klassen haben,
so das Gegenteil von abstrakten Klassen,
dass nur so zu der Folie her ist,
dass es eben alles, was sie bis jetzt kennengelernt haben,
an Klassen waren konkrete Klassen, weil die eben keine abstrakten Methoden enthalten haben.
Jetzt kommen wir noch zu einem, also das ist jetzt,
denke ich, dieses abstrakte Methoden aufrufbar sind, folgt eigentlich aus dem,
worüber ich jetzt gerade gesprochen habe, Wir haben darüber gesprochen,
dass Methoden in einer Klasse auch Methoden aus dieser Klasse aufrufen können.
Wenn das eine abstrakte Methode ist, dann ist es halt so,
das ist deswegen unschädlich, weil von dieser abstrakten Klasse,
die eine abstrakte Methode aufrufen würde, wird es niemals ein Exemplar geben,
weil von abstrakten Klassen keine Exemplare erzeugt werden können.
In dem Moment, in dem eine Implementation vorliegt,
können von dieser Klasse auch Objekte erzeugt werden,
was ungefährlich ist, weil dann liegt ja eine Implementation vor.
Also so bekommt das Ganze doch, wird es so eine
runde Geschichte, die im Programm Familiealltag durchaus Sinn ergibt.
Zum Abschluss meines Teils werde ich Ihnen jetzt noch eine spannende,
ach, ich sollte das nicht bewerten,
ich werde Ihnen noch eine Geschichte erzählen, finden Sie selbst raus,
ob Sie das spannend finden oder auch nicht.
Eine Methode, eine Programmiermethode. Wir werden im Laufe des Semesters
noch über sogenannte Entwurfsmuster sprechen. Die Schablonenmethode mit Einschubmethode ist ein solches Entwurfsmuster,
das sind um nicht schon mal kurz vorzugreifen,
Entwurfsmuster sind so, so Mikroarchitekturen für Softwaresysteme
Das sind so Lösungsmuster für bestimmte Problemstellungen, die immer mal wieder auftauchen.
Und diese Sprache werden wir ihnen im Laufe des Semesters und
noch im Laufe ihres Studiums werden wir ihnen das beibringen.
Sodass Schablonenmuster, worin besteht das? Die Idee ist,
es gibt einen Kernalgorithmus und dieser Algorithmus ist fix,
den kann ich definieren. Also zum Beispiel in einer Oberklasse
. Das ist die Schablonenmethode selbst. Und dieser Kernalgorithmus,
der bedient sich zu seiner Realisierung sogenannte Einschubmethoden, also Methoden,
die er aufruft, um diesen Kernalgorithmus ablaufen zu lassen.
Das ist eine Technik, die wird zum Beispiel bei der
Implementation von Frameworks, wird der gerne verwendet.
So, Frameworks, wovon redet er jetzt?
Wir werden auch noch kennenlernen, wie man interaktive Oberflächen gestaltet.
Und das, was sie ja tagtäglich tun,
ist, dass sie so auf Knöpfe drücken,
wie zum Beispiel okay oder über Abbrechen oder irgendwas, von der er kannte.
Und was sie kennenlernen werden, ist, dass das Gesamte,
die gesamte Ereignisverarbeitung beruht darauf, dass jemand sagt,
also es gibt einen Knopf und es gibt auch eine Operation, die gerufen wird.
Genau dann, wenn dieses Ereignis Knopf drücken, genauer gesagt,
Knopf loslassen, erzeugt wird.
Und dann kommt es bei Ihnen an und die Standardreaktion darauf ist,
ich meine nichts, gar nichts.
Dann müssen Sie halt mit dem Mitteln der Redefinition,
das werden wir alles noch kennenlernen, müssen Sie ein entsprechendes Verhalten implementieren.
Aber das Prinzip ist genau das, was ich jetzt gerade
beschrieben habe
Eine Schablonenmethode. Da ist in einem für sie
nicht erkennbaren Kontext, ist ein Algorithmus implementiert.
Und sie sind diejenigen, die die Einschubmethoden implementieren,
damit etwas, also in ihrem Sinne passiert.
Aber die Reihenfolge, in der es passiert, die Reihenfolge,
diese Entscheidungen werden von jemand anders getroffen.
Das ist, man nennt es diese Schablonenmethode, auch das sogenannte Hollywood-Prinzip.
Das Hollywood-Prinzip, soweit wie ich das aus Informatikerbüchern kenne,
heißt Don't Call Us,
we call you
Also man soll nicht den Agenten anrufen, habt ihr eine Rolle für mich?
Und wenn man das fünfte Mal angerufen hat,
sagt er, don't call us, we call you.
Und das ist das Prinzip, das bei diesen Einschubmethoden auch verwendet wird. Ein Beispiel,
dass genau diese Mechanismen ausnutzt, über die ich jetzt gerade gesprochen habe,
ist also so eine abstrakte Klasse, Titelliste, die soll,
also das ist halt, was die tut, ist,
man kann damit eine Menge von Titeln in Form einer Liste kann man damit verwalten.
Dann stellt die diese typischen Operationen zur Verfügung. Also ich kann sowas
wie einen Titel hinzufügen. Und wenn ich durch das Ding,
durch diese Titelliste so mal so durchgehen möchte,
dann gibt es also so die Atomaren, diese Einschubmethoden namens,
also bewegt dich mal an den Anfang der Liste.
Gib mir so ein Item und dann geh auf das nächste
und dann gucke ich mir das noch wieder an.
Und solange ich nicht, also hinter der Liste bin, kann ich auch weitersuchen.
So, jetzt kann ich was der tückische Autor dieser Klasse.
Deswegen hat er die Abstract gemacht, das ist nicht tückisch.
Also das ist der clevere Autor dieser Klasse, sich jetzt denkt,
ist, je nachdem, was das für eine Liste ist,
so wird ich jetzt nicht die Entscheidung treffen, wie die implementiert werden soll. Also,
das, was sie jetzt sich zum Beispiel in Kenntnis dessen,
worüber wir in SE1 gesprochen haben,
sie können das als Linklist, können sie das implementieren,
sie können das als Array implementieren, können sie noch was,
auch als Stack, das wäre zwar Quatsch,
aber möglich ist es,
wer nur einen Hammer hat,
für den ist die Welt
ein Nagel
So, diese Entscheidung will derjenige, der diese Appstract-Titellist
zur Verfügung stellt, für sie einfach nicht treffen.
Guter Mann, gute Frau, so ein gutes, gutes Kind.
Geschlechtsneutral formuliert. Was er aber tun kann, ist,
was es tun kann, ist,
es kann schon mal eine Operation Contains implementieren.
Und das ist jetzt so,
das ist jetzt fast so eine Aussage wie schwarzer Schimmel
Also, weil weißer Schimmel geht nicht, so als doppelt gegomoppelt,
schwarzer Schimmel, ist Quatsch, weil also Schibbel sind immer weiß.
Das da ist eine abstrakte Implementation, die ist deswegen abstrakt,
weil sie beruht ausschließlich auf abstrakten Operationen.
Die ganzen Operationen, die hier in Contains verwendet werden,
die gibt es noch, also die ganzen Implementationen gibt es noch nicht.
Es gibt weder Empty-Start, off-maxed Item noch auf,
ist alles noch offen, aber die Implementation kann ich trotzdem schon vornehmen,
weil ich weiß ja,
wie das funktionieren wird,
wenn es dann
mal implementiert sein wird
Das ist doch toll. Also ich weiß noch nicht genau,
wie das funktionieren wird, aber wenn es funktioniert, dann tut es das auch.
Also kann ich schon mal diese Operation Contains und alles,
was darauf basiert, kann ich schon mal implementieren.
Und sie können jetzt in einer konkreten Klasse Titelliste, sagen sie halt,
okay, ich deklariere mir hier mal ein RAI und jetzt sage ich,
was bedeutet es, Start an einem, an einer Titelliste aufzurufen,
okay, ich setze mal den Zeiger auf das Aktuellelement,
Element auf Null.
So, und dann gucke ich halt,
ist das Ding leer und dann kann ich halt meine RAI-Implementation verwenden,
weil mir für das Problem, das ich zu überarbeiten habe,
Array als die angemessene Implementation erscheint.
So, und jetzt haben Sie das Mittel der abstrakten Implementation
kennengelernt und das können Sie eben auch wunderbar verwenden,
um ein Verhalten erst einmal zu spezifizieren, damit es später jemand anders implementiert.
Wenn ich jetzt von so einer Reihenfolge spreche,
erst Spezifikation in einer Oberklasse und dann Implementation in einer Unterklasse,
das ist ein Weg, wie man damit umgehen kann.
Der andere Weg wird genauso häufig gegangen. Sie schreiben eine konkrete Klasse,
gewinnen Erfahrung, verwenden die und dann können Sie auf die Idee,
Nun, ich möchte so was ähnliches nochmal machen und dann abstrahieren sie von dem,
was sie in der konkreten Klasse haben, tun das in der Oberklasse,
sei die jetzt abstrakt oder nicht, mag die Schablonenmethoden haben oder nicht.
Und dann können sie daneben die Klasse schreiben, die das ähnliche Problem löst.
Also das ist von der Reihenfolge her so aus einer Klassenhierarchie.
Können sie typischerweise nicht sehen, in welcher Reihenfolge, die
darin beteiligten Klassen entstanden sind. Es gibt jetzt noch so ein paar Schlüsselwörter,
die in diesem Zusammenhang eine Rolle spielen, also zum Beispiel Final,
so wenn das als Final, wenn irgendwas als Final deklariert worden ist,
dann kann das in einer Unterklasse nicht mehr definiert werden.
Bitte, das ergibt auch manchmal Sinn, also ob das jetzt hier,
die Strings ist ja immer so eine Sonderfall, also nur,
dass sie wissen, warum man von String keine Unterklassen bilden kann,
das ist als Final, ist das deklariert. So,
15 Uhr 1 in meiner Zeit ist es vorbei,
sie werden sehen in den Folien,
die ich zu dieser Vorlesung ins Netz gestellt habe,
es sind wie immer mehr Folien,
ich habe einen Aspekt jetzt hier ausgelassen, von dem ich glaube,
dass sie aber in den Folien extrem gut erklärt ist,
ansonsten Die Java-Literatur sagte auch sehr viel zu,
das ist der Umgang von,
wie verhält es sich mit Konstruktoren und Vererbung?
Da gibt es also so ein, zwei Dinge,
die man wissen muss, aber ich denke, das ist,
ich hoffe es jedenfalls, ist anhand der Folien,
ist es gut erklärbar.
So, Vorlesung 4 von meiner Seite aus vorbei.
And now for fred, ich wünsche Ihnen ein schönes Wochenende.
Okay, ich habe nun mal Eclipse gestartet,
weil das dauert ja immer so ewig.
Ich wollte eigentlich das hier starten
Gut. Also, es hat mich relativ viele Betreuer darauf hingewiesen,
dass ihr, oder dass einige von euch die schriftlichen Aufgaben verweigert.
Also, wenn irgendwo so ein Kugelschreiber-Symbol auf dem Platt ist und da steht Fett,
bitte macht es schriftlich oder schreibt es auf, dann macht es auch wirklich.
Sonst könnt ihr die Abnahmen nicht kriegen. Es gibt da
angeblich Betreuer, die nicht ganz so kritisch sind,
aber eigentlich müsst ihr das immer schriftlich machen.
So, habe ich gemacht. Dann hat mich jemand per
Mail gefragt, kann man eigentlich auch sein Programmatik-Tat einsehen,
dass man irgendwie mal sieht, was die Fehler waren.
Das ist so vorgesehen, also Till wird in der kommenden
Woche in alle Labortamie da reingehen und würde immer sagen,
Diktat von Max Mustermann ist er anwesend.
Wenn ja, hat er Interesse,
seinen Diktat einzusehen und dann könnt ihr das halt mit ihm
einsehen und er erklärt euch sogar, was eure Fehler sind.
Tja, also da müsst ihr euch nicht speziell für anwenden,
sondern das passiert einfach automatisch.
Gut, dann haben wir was Komisches festgestellt.
Also die Kapazität für Dimi ist ja 80 Leute.
Und ich glaube, es sind irgendwie 78 angemeldet.
Das Diktat haben aber irgendwie 95 Leute mitgeschrieben. Und anwesend waren,
glaube ich, 115 oder so. Das ist ein Problem,
weil wir natürlich unsere Raumkapazitäten für Laborphase 2 sind so
ein bisschen, ja, wie soll ich sagen,
Da muss man ein bisschen mehr aufpassen als in Laborphase 1.
Das ist jetzt ja nicht so ein Problem,
wenn ein paar mehr Studenten da sind in Laborphase 1.
Aber in Laborphase 2 ist es nicht so lustig,
weil wir pro Raum eigentlich nur so zwischen 16 und 20 Leuten zulassen.
Und wir haben nur fünf Räume. Ja,
also wir können, wir können so wahnsinnig,
oder haben wir nur vier Räume oder wir lassen nur 16 zu,
ich weiß es nicht genau.
Auf jeden Fall, es werden sehr viel weniger Leute in
den Räumen drin sein und wir haben das sehr genau kalkuliert.
Und deswegen müsst ihr irgendwie dafür sorgen,
allerspätestens ab Woche sechs, wenn mich nicht alles täuscht,
dass ihr in eurer regulären Termine reingeht.
Am besten schon jetzt in der kommenden Woche,
weil ihr sollt ja auch Leute finden,
mit denen ihr zusammenarbeitet und so.
Also bitte seht zu,
dass ihr in eure regulären Termine reingeht
Okay, ich weiß nicht, ob euch das programmiert hat,
Spaß gemacht hat. Ich habe mir mehrere hundert Stück angeguckt.
Das macht immer Spaß. Und man findet da immer so
ein paar Dinge, zu denen man gerne noch was sagen will.
Und ich habe mir immer eins rausgesucht,
damit das nicht zu viel vom eigentlichen Thema ablenkt.
Also, es gab mehrere Stellen oder eine Stelle in mehreren Dektaten,
wo man eine Variable um einen implementieren sollte.
Und am einen so eine Handvoll Leute oder vielleicht auch ein
Dutzend, haben sowas geschrieben. Die haben gesagt,
um eine Variable Index zu erhöhen, schreibe ich Index,
gleich Index plus plus.
Schauen wir mal,
was da passiert
Ich starte das einfach mal. Ihr seht, also die
wird am Anfang auf sieben gesetzt. Dann kommt dieser Erhöhungsversuch
und dann wird das einmal vorher und immer nachher ausgegeben.
Schauen wir mal, was rauskommt. Ja. Hat
diese Zeile jetzt irgendwie gar keinen Effekt? Das ist ja
irgendwie komisch, oder? So, und dazu habe ich
jetzt nochmal was vorbereitet. Das ist so eine Sache,
die kann ich in C besser erklären als in Java,
weil es C so komisch ist.
Okay, ich möchte euch nochmal den Unterschied klarmachen zwischen zwei
ganz zentralen Begriffen aus
Wirkung und Ergebnis statt Wirkung sagt man auch oft auch Nebenwirkungen oder Seiteneffekt.
Das sind alles Synonyme. Also was bewirbt denn eigentlich
ein Ausdruck? Und was ist das Ergebnis des Ausdrucks?
Das bringt man gerne mal durcheinander. Das muss ich einmal
kurz hier auseinanderzerren. Also, was glaubt ihr, was
die Wirkung von diesem Ausdruck ist? Ihr kennt jetzt nicht C,
aber vielleicht könnt ihr euch ja denken, na,
Print, das könnte irgendwas bedeuten.
Hat ja mal eine Idee, was die Wirkung dieses Ausdrucks
sein könne. Was passiert da? Hat das
irgendeinen Effekt
Achso, du meldest dich gar nicht.
Ja, es wird Text auf die Konsole geschrieben.
Das ist es auch schon. Das ist die Wirkung.
Also ich werte diesen Ausdruck unter anderem deswegen aus,
weil ich will, dass etwas auf der Konsole erscheint.
Gut. Jetzt hat dieses Print einfach mal nicht nur eine Wirkung,
sondern auch ein Ergebnis. Das seht ihr daran,
dass ich diesen Ausdruck einer Variable zuweisen kann vom Typ Int.
Das geht deswegen, weil der Ergebnistyp von Print of Int ist.
Das heißt also, hier kommt irgendeine Zahl raus.
Okay. Wollen wir mal gucken, welche Zahl dabei rauskommt
Und das ist immer ein bisschen kleiner. Und da setzen
wir das einmal, wie heißt dieser Teil, Pi.c.
Und führen wir es mal aus. Ihr seht also,
das steht jetzt hier auf der Konsole,
weil das die Wirkung von diesem Print-Effe ist.
Das Ergebnis des Print-Effs war offenbar 19,
weil ich habe die 19 in dieser Variable abgespeichert und habe
dann diese 19 nochmal auf die Konsole geschrieben.
Warum 19? Das ist jetzt nicht besonders spannend. Print-Eff liefert euch einfach
als Ergebnis, wie viele Zeichen auf die Konsole geschrieben wurden.
Und das sind in diesem Beispiel 19. Und immer,
wenn ihr irgendwie durcheinander kommt, was ist denn nochmal Wirkung,
was ist nochmal Ergebnis?
Denkt einfach an Print-F, nehmt Print-F, hallo.
Die Wirkung von Print-F, hallo, ist es Hallo auf
die Konsole zu schreiben? Und das Ergebnis ist fünf,
weil fünf Zeichen auf die Konsole geschrieben wurden,
Das finde ich schon ganz nett, um das nochmal zu verstehen,
was der Unterschied ist zwischen diesen beiden Konzepten.
So, und jetzt ist mal eine Frage an euch,
was ist die Wirkung von Index plus plus und was ist
das Ergebnis von Index plus plus?
Tut sich irgendwas, wenn ich In-Explus plus wird,
was auf die Konsole geschrieben, wenn ich In-Explus plus auswerte?
Wahrscheinlich nicht. Was kann sich denn sonst so tun?
Was kann sonst so für Wirkung geben,
Ähm, du hast jetzt schon beides irgendwie so ein bisschen durcheinander gewürfelt.
Also kann vielleicht noch mal jemand anders mir einmal beantworten,
was macht dieses Index plus plus?
Wird irgendwas passiert da irgendwas? Ich will gar nicht wissen,
was das Ergebnis ist, ja. Ja, das ist
bei dir. Erstmal in den Himmel, selber als Karnierabend,
ein Fühl. Stimmt. Punkt, fertig, richtig.
Die variable Index wird um einen erhöht. Das ist die
korrekte Antwort auf die Frage, was ist die Wirkung?
Sehr gut. Und was ist das Ergebnis von Index plus plus?
Da vielleicht mal jemand anders? Ja. Ja,
das Prinzip eher der Namen, der 81001 Erdmal
würden wir erleben
Mit dem Thema zu vergessen, die Montagnahmen. Es ist seltsam, das stimmt,
ja. Jemand anderes noch eine Idee, ja? Nein,
wir sind in der, wie ganz gut gehalten war,
in der sich selber etwas zurückgefallen, in die Rechnung,
die wir jetzt dann nahmen können.
Und dann war eine Schule in Textel am Bänder,
aber weil ich mich gerade mal zugefügt habe,
dass wir erst alles innerhalb der Definition,
von denen wir groß gewählt haben,
und dann nur die Entdeckung gekommen sind.
Ja, also du gehst schon viel, viel,
viel zu weit. Hat jemand eine einfachere Antwort,
was das Ergebnis ist von Index plus plus?
Uns dabei. Das ist die ganze Zeit. Das ist
auch nicht die Antwort, die Frage. Ich lasse noch
einen und sonst löse ich das Rätsel auf, ja?
Index? Ja, also es wird der Wert zurückgegeben,
bevor erhöht wurde sozusagen, okay? Die Zeile ist der Grund dafür,
warum ich ein Gegner des Postfix plus plus bin.
Ich habe also noch nie in meinem ganzen Leben jemanden getroffen,
der mir Fehler fahre, beschreiben konnte, was Indexbox plus eigentlich wirklich macht.
Okay, gut, also ich löse es nochmal ganz kurz auf.
Hier passiert also folgendes. Die Variable Index wird auf acht gesetzt
Und das Ergebnis ist sieben und diese sieben wird jetzt zugewiesen in die Variable Index.
Das heißt, sie wird einmal auf acht gesetzt und anschließend
wird sie wieder auf sieben zurückgesetzt.
Ja, das ist das, was da wirklich passiert.
Okay, das ist ein kleiner Ausflug, was man so alles falsch machen kann.
Gut, jetzt gehe ich wieder zurück zu meiner Auflistung,
was ich alles mit euch machen will.
Genau, wir reden ja eigentlich, heute ist das Thema ja Vererbung.
Darum soll es ja gehen. Deswegen,
ich hatte euch das schon versprochen,
möglicherweise in Woche eins oder in Woche zwei,
dass wir dann nochmal mit der Vererbung an den Code rangehen wollen,
um da ein bisschen Code-Duplizierung rauszubekommen.
Also, Ihr erinnert euch noch in den Wochen 1
und 2 haben wir unseren eigenen Stack gebaut.
Und ein Stack hat ja eine Operation. Push. Ich kann sagen,
ich will einen neuen Stack haben, wo ein weiteres Element oben drauf ist.
Und wenn ich jetzt zum Beispiel auf dem leeren Stack sage,
hey, lieber Lehrer Stack, ich will ein Element auf dich rauftun,
was kommt dann zurück?
Dann kommt ein nichtlehrer Stack zurück, wo das Element,
ja, was ich obendrauf haben will, obendrauf.
Und da drunter ist dann der leere Stack.
Ich selber bin ja der leere Stack innerhalb dieser Klasse.
Gut. Und wie sieht das Push-Off aus auf einem nichtleeren
Stack? Kann ich das vielleicht so scrollen
Das komme ich auch mal nach oben, dass hier genau den Unterschied,
ich switche jetzt immer so hin und her.
Und dann schaut ihr mal, seht ihr den Unterschied?
Kann jemand mir den Unterschied sagen, zwischen den beiden Implementationen von Push?
Was ist der Unterschied? Ja. Es gibt keinen Unterschied
, richtig. Sehr gut. Eine Fangfrage. Gut,
das heißt, das wäre ein idealer Kandidat,
um zu sagen, ich will es nur einmal programmieren.
Also bis jetzt kennen wir ja folgendes,
wir haben ein Interface und wir haben beliebig viele implementierende Klassen.
Und wenn jetzt irgendwo in diesen implementierenden Klassen derselbe Code nochmal ist,
Und wenn wir das jetzt nicht in das Interface reinziehen,
zumindest nicht bis Drafa 7, sondern wir müssen jetzt irgendwie so eine Zwischenstufe einbauen.
Interface, konkrete Klasse und dazwischen eine abstrakte Klasse,
wo alle Gemeinsamkeiten der konkreten Klassen irgendwie zusammengefasst werden.
Und in diesem schönen einfachen Beispiel ist es nur diese eine Methode.
Sonst schreiben wir diese abstrakte Klasse doch mal, die wir dazwischen schieben wollen.
Also sage ich, ich will eine neue Klasse.
Und jetzt kann man hier bestimmt auch einstellen, dass sie abstrakt sein soll.
Ich gucke, ach guck mal, das kann man hier einstellen.
Public brauche ich noch nicht, das kommt erst nächste Woche.
Wie wollen wir sie nennen? Also wir haben das Interface
per Sistant Stack, dann haben Venille und Cons,
ich nenne sie einfach mal Appstract Stack.
Wenn einem nichts Besseres einfällt, ein Name für eine
abstrakte Klasse fängt meistens mit Appstract an
So. Gut, hier ist also meine Abmstrakte Klasse.
Wie sorge ich jetzt dafür, dass die dazwischen geschoben wird?
Also ich muss diese abstrakten Klasse sagen, du implementierst das Interface Persistant Stack.
So. Und komischerweise beschwert sich der Compiler gar nicht.
Wir haben doch bis jetzt immer gesagt, wenn eine Klasse ein Interface implementiert,
dann muss ich doch für alle Operationen des Interfaces Methoden anbieten,
das tue ich jetzt gar nicht.
Komisch, oder? Weil die Klasse Abstract ist,
genau. Wenn ich da so das App-Stract hier wegnehme,
Dann warmt mich der Compiler und sagt, pass auf,
wenn das gehen würde, dann könntest du ja zur Laufzeit Exemplare dieser Klasse erzeugen.
Und was soll dann passieren, wenn jemand Push oder Pop
aufholt? Ich habe ja gar keinen Code dafür.
Aber wenn ich es abstrakt mache, dann ist garantiert,
dass niemand jemals ein Exemplar dieser Klasse erzeugen wird.
Das geht einfach nicht, das verbietet die Sprache.
Also ist es kein Problem. Gut. Jetzt ziehen wir
doch mal den Coter hoch. Also hier dieses Push,
das nehme ich jetzt mal hier raus.
Steuerung X. So. Und tu es mal hier rein.
Zack. Und dann nehme ich es aus Kunz auch mal
raus, dass wir gleich diese Duplizierung weghaben
So. Ja, jetzt ist ja Compiler natürlich noch nicht zufrieden,
weil diese klasse Cons hat jetzt ja noch gar keine Verbindung zu diesem Abstract Stack.
Wie kann ich denn jetzt sagen,
ich will nicht mehr direkt dieses Interface implementieren,
sondern ich will diesen Umweg gehen über diesen Abstract Stack.
Ja. Genau. Ich sage also,
an dieser Stelle nicht, ich implementiere ein Interface,
sondern Extenz, Apps, Stract Stack.
So,
und das mache ich bei der anderen Klasse
genauso.
Gut, und jetzt geht hoffentlich noch alles.
Wir haben ja leider keine Tests dafür,
wir können das vielleicht noch einmal ganz kurz starten.
Und mal gucken, ob jetzt nicht ein kompletter Unsinn dabei rauskommt.
Ihr seht, das ist immer noch, ihr erinnert euch
wahrscheinlich gar nicht mehr. Wir hatten ja gesagt,
wenn alles funktioniert am Ende von Vorlesung 1, dann müsste da sieben rauskommen.
Und es kommt immer noch sieben raus, das ist doch schön.
Ja. Okay, gibt es zu diesem allerersten einfachen Beispiel
noch Fragen? Das ist eigentlich nur das simpelste Beispiel,
was man sich vorstellen kann. Zwei Klassen,
in denen dieselbe Methode zweimal drinsteht,
ich ziehe sie hoch in eine abstrakte Klasse und muss noch dafür sorgen,
dass die sinnvoll in dieser Vererbungshierarchie drinstehen.
Also die abstrakte Klasse implementiert das Interface und die konkreten Klassen
Erben von der abstrakten Klasse.
Ist das irgendwie klar
Ja, oder? Ja. Auch nicht.
Ich bin nicht quasi, ich frage mich das,
was unser Schritt hat, sozusagen zusammen in einer Aussage.
Ah, die Frage ist, ob wir das Interface überhaupt noch brauchen.
Also könnte man nicht einfach hingehen und sagen, ich nehme dieses Interface.
Und ich könnte ja im Prinzip alle Operationen,
die hier drin stehen, als abstrakte Methoden in den Appstract
Stack machen und dann lösche ich das Interface einfach.
Ja, das kann man machen. Das mache ich jetzt aber nicht,
weil das ist relativ mechanisch und dann sind wieder fünf bis zehn Minuten weg.
Und dann muss ich mit euch klären, was jetzt ein schöneres Design ist.
Aber das geht, klar. Also man kann sich eigentlich vorstellen,
zwischen einem Interface und einer abstrakten Klasse, wo ausschließlich abstrakte Operationen drin sind,
da gibt es jetzt nicht so wahnsinnig viele Unterschiede.
Gut. Ja, du würdest dich, ich gebe es jetzt im Spiel,
wenn wir welche Methode nicht dran hätten, die Push-Methode?
Das heißt, wenn ich sie aus dem App-Stract-Stack wieder rausnehme?
Ja, da würde er sich beschweren. Wir können sie
ja mal auskommentieren. Dann beschwert er sich jetzt wieder an
diesen beiden konkreten Klassenungsaufgast, die fehlt die Methode, ne?
Time will must implemently enharited app-Stract method persistance stack push
Die ist also nirgendwo auf diesem, in diesem Familienbaum sozusagen irgendwo implementiert.
Sehr schön. Ja, keine weiteren Fragen. Gut,
dann gehe ich mal wieder zurück. Zu meinem Skript.
Was habe ich noch? Das haben wir jetzt gemacht.
Okay. Und dann kommen wir jetzt zu dem Projekt von
letzter Woche. Da hatten mich zwei Lösungsvorschläge erreicht.
Und ich hatte so ein blödes Problem.
Ich habe mir diese Lösungsvorschläge angeguckt und wusste nicht genau,
wie teste ich denn jetzt, ob die richtig sind oder ah,
jetzt habe ich schon verraten.
Wie prüfe ich denn jetzt,
ob die richtig sind oder nicht
Prüfe ich dann auch die Lösung, die ich mir irgendwann
mal ausdenke, wer da richtig ist oder nicht.
Da dachte ich, naja, wir können ja Tests schreiben.
Wir sind doch, wir haben saubere und erfahrene Programmierer.
Wir schreiben einfach einen Test, ob diese Sache mit den Klammern.
Ihr erinnert euch, ich will keine überflüssigen Klammern generieren.
Ob die irgendwie korrekt implementiert ist, also habe ich mal
einen Test geschrieben, wollen wir uns den mal ganz kurz angucken.
Also diese Testklasse kennt einen Parser. Okay, gut.
Dann habe ich mir noch eine kleine Hilfsmethode geschrieben.
Sie kriegt also einen String in dieser Postfix-Notation rein, also irgendwie A,
B und oder so und liefert mir einen String in Infix-Notation zurück.
Das ist einfach nur, damit ich nicht immer mit
diesen Parser an allen möglichen Stellen reden muss.
Das ist nur eine Hilfsmethode, ignoriert die erstmal.
Und hier seht ihr jetzt, das ist relativ lesbar,
Ich sage also, die Infix-Notation von A, Entschuldigung,
die... Nee, das ist ja die Infix-Notation.
Also die Infix-Notation sieht ja genauso aus wie die Postflex-Notation.
Da sollte es keinen Unterschied geben. Ich will also nicht
, dass hier irgendwelche Klammern entstehen oder so.
Wenn ich zum Beispiel A negiere, dann sollte das so aussehen,
nicht A und auch irgendwie nicht zusätzliche Klammern.
Letzte Woche hatte ich da ja noch zu zusätzliche Klammern.
Dasselbe soll auch zum Beispiel gelten, wenn nicht, mehrere Außenverzeichen hatte.
Also mir zum Beispiel eine Lösung wurde mir geschickt,
die hat für ein Ausrufezeichen funktioniert, aber für zwei hat sie nicht funktioniert.
Wollen wir vielleicht nicht so oft denkt, dass jemand sowas braucht, doppelte Integration.
Okay, dann habe ich nochmal einen Test geschrieben,
dass das für unfunktioniert, A,
B und sollte A und B sein,
A,
B oder sollte A oder B sein
Ich das Ganze nochmal mit negierten Ausdrücken gemacht,
weil durch die sind ja auch wichtig.
Und dann wurden mir immer ein Langweiliger, habe ich überlegt,
was ist mal ein wichtiger, wirklich praxisrelevant davon?
Oder was macht zumindest mehr Spaß? Und das sind diese beiden
. Die finde ich einfach toll, weil man kann,
wenn man das exklusiv oder in seiner Sprache nicht drin hat,
kann man sich das simulieren.
Bei uns ist das an einem Beispiel mal angucken.
Nehmen wir mal hier. Also. Ja, was heißt
denn? Das ist genau eine der beiden Variabien gilt.
Das heißt doch, dass mindestens eine von beiden gilt,
das sollte ja dieser Ausdruck, aber was auch gelten muss,
es dürfen nicht beide gleichzeitig gelten.
Also mindestens eine, aber nicht beide gleichzeitig.
Das ist exklusiv oder
Und das ist eben interessant, weil hier zwei Disjunktionen mit
einem Hund verknüpft werden und dafür brauche ich eben Klammern.
Während hier oben, brauche ich die Klammern nicht.
Ja. Okay. Und dieser Testfall hat auch in meiner eigenen Implementation
mehrere Fehler aufgedeckt. Ich bin total stolz auf mich.
Gut, also wegen jetzt Tests, nicht wegen der Implementation.
Wollen wir den Test einmal ausführen und gucken, ob er klappt?
Ja, sieht gut aus, ne? Alle Tests erfolgreich durchgelaufen.
Das war nicht immer so. Ich habe das jetzt für
die Lösung, die mir zugeschickt wurden, noch nicht gemacht.
Dafür hatte ich einfach nicht genug Zeit.
Ich bin mal gespannt, ob die auch dann alle
letzten Endes funktionieren werden
Okay, dann schauen wir es mal an, wie ich
das umgesetzt habe. Gehen wir nochmal in das Interface Expression.
Letzte Woche gab es nur diese Operation Infixform. Ich habe
eine Expression und sagte Expression, gib mir doch mal eine Infixform.
Das haben wir letzte Woche zusammen implementiert. Ich habe das
jetzt mal hier einen Kommentar dazu geschrieben. Ich lerne auch
immer gerne ein bisschen, wie man Java Doc schön schreibt.
Jetzt kommen auch mal wieder so Fragen an der Übung,
hey, was heißt das eigentlich,
wenn das so komische Rauten sind und so komische,
geschweifte Klammern und so.
Das gucken wir uns einmal ganz kurz an,
weil das finde ich, ist für die Praxis ganz nett.
Also wenn ihr irgendwelche Kotabschnitte habt in euren Java Doc Kommentaren und ihr wollt,
dass die auch so schön in Code Fond irgendwie sichtbar sind,
dann tut ihr die einfach in so geschweifte Klammern,
die geschweifte Klammertums irgendwo hier hinten,
da ist sie
Und schreibt, add code davor. Wenn ihr das macht,
seht ihr das hier unten sehr schön, dann seht ihr hier,
das ist jetzt so ein schöner Incode von formatierter Javato-Kommentar.
Das ist schon mal ganz nett. Okay,
und dann habe ich hier irgendwo noch so ein Ding,
das wollte ich auch gleich zeigen, hier zum Beispiel.
Wenn ihr also zum Beispiel auf andere Methoden von euch verlinken wollt,
könnt ihr sagen, add link,
schreibt ein Auto davor und dann zum Beispiel an Methoden am Seht
ihr das hier um,
dass hier so ein Link entstanden ist.
Und der Link funktioniert auch sogar. Also ich kann sagen,
okay, das interessiert mich, was sich hinter dieser Methode verbirgt.
Klicke ich drauf und dann lande ich in der Methode da oben.
Also im Kommentar, nicht im Code.
Okay, das ist so als kleines Schmankerl zwischendurch
Okay, und wenn man sich schon ein bisschen Gedanken macht,
ich weiß nicht, wer von euch sich Gedanken gemacht hat,
wie man denn jetzt das Problem lösen kann, keine zusätzlichen Klammern zu generieren.
Dieses Konzept der Klammern hat ja sehr viel mit Operatorkrezedenzen zu tun.
Also manche Operatoren binden stärker als andere. Wenn man das
so haben will, wie es nach der Operatorkrezedenz geht,
dann brauchen wir halt keine Klammern, ansonsten braucht man eben welche.
Also zum Beispiel die höchste Präzedenz hat das nicht und die niedrigste hat das oder zum Beispiel.
Ich habe also hier einfach eine Operation hingeschrieben,
dass ich jede Expression fragen kann, hey,
du bist doch irgendwie so ein und oder ein oder oder nicht oder so,
gib mir doch mal deine Präzedenz, damit ich irgendwie weiß,
was ich mit dir machen soll.
Okay, und hier, ich habe ewig gebraucht,
um einen schönen Namen zu finden,
damit sich das
irgendwie schön liest
Also ich kann eine Expression jetzt auch fragen,
gib mir doch mal so eine Infix-Form,
aber ich verrate dir, was meine eigene Präzedenz ist.
Ja, ich kann doch sagen, pass auf,
meine Präzedenz ist irgendwie null. Ich bin zum Beispiel eine Integration
und dann sage ich ja zum Beispiel, I'm end,
gib mir doch mal deine Infix-Form, die für mich geeignet ist.
Shootable Fore heißt ja geeignet für irgendjemanden. Okay,
das heißt, dann kann diese Methode intern entscheiden,
oh, das draußen ist ja nicht,
ich bin selber aber nur ein End,
dann muss ja geklammert werden.
Das ist so die Grundidee der Lösung. Es gibt
ganz viele verschiedene Möglichkeiten, wie man das lösen kann.
Das ist die, die mir am elegantesten erschienen.
Okay, jetzt fangen wir wieder mit den einfachsten Fällen an.
Also schauen wir doch mal,
wie das gelöst ist in
Gut. Hier ist eigentlich fast egal,
was man da hinschreibt, weil hier ist ja gar kein
Operator drin und ich habe da einfach mal null zurückgegeben,
weil man so ein A nie klammern muss.
Also es ergibt irgendwie keinen Sinn, ein A an sich zu klammern.
Das muss man nicht machen, egal was da drumherum steckt.
Okay, und ihr seht auch schon,
ich kann hier einfach per Delegation die normale Infex-Form zurückgeben.
Das interessiert mich gar nicht, wie man in der Surrounding
Presidence ist. Das ist schon eine blöde Default-Implementation.
Null und die normale Methode. Könnt ihr euch vielleicht schon
vorstellen, in B sieht das genauso aus.
Null und die normale Methode und dann schauen wir uns noch
das Not an
Was haben wir hier? Null und die normale Methode.
Was machen wir jetzt? Wir haben dreimal denselben Code.
Ich komme jetzt zurück zum eigentlichen Thema der Vorlesung.
Wir haben ein Interface, wir haben fünf implementierende Klassen und
drei davon haben irgendwie exakt dieselben beiden Methoden implementiert.
Was machen wir da? Irgendwie mal eine Idee,
so ganz grob, ja? Ja, genau,
man könnte eine abstrakte Klasse mit ihm nehmen,
man vielleicht person wenn es null.
Ich hatte mir überlegt, wir nennen sich vielleicht einfach Simple Expression,
aber dein Vorschlag wäre genauso gut gewesen.
Ich nenne sie jetzt einfach Simple Expression,
weil das in meinem Hirn schon so einprogrammiert ist quasi.
Gut, machen wir das doch mal.
Also eine neue abstrakte Klasse
Wir machen sie Package-Preude, wir machen sie App-Strack und nennen sie
Simple Expression. Kann ich ja auch sagen, welche,
ach guck mal, man kann ja auch Interfaces hinzufügen.
Ich will das einfach mal ausprobieren. Ihr seht,
ich habe jetzt schon mehr Zeit verloren,
als wenn ich das einfach so gemacht hätte.
Okay. Gut, hat er das jetzt? Ja. Toll,
oder jetzt muss ich das nicht mehr von Hand schreiben.
Okay. Das lösche ich mal, das ist ja in allen Klassen verschieden.
Und das kann ich jetzt im Prinzip übernehmen, aus A,
B oder noch, das ist völlig egal.
Ich übernehme es mal aus, A, macht aber überhaupt
keinen Unterschied. Ich schneide das jetzt also hier aus A
einfach raus
Die Präzedenz Null und diese Infixform Suitable Fonds, Surrounding Presidents.
Parke das hier rein. So. Okay. Dann lösche ich
es erstmal aus dem B, dass ich das nicht vergesse.
Dann wird die Klasse schon gleich viel übersichtlicher, finde ich.
Dann ist nur noch eine Methode, dann noch nicht drei.
Dann versteht man den Sinn und die Kernidee viel besser von dieser Klasse.
Und hier genauso. So, jetzt muss ich wieder an
diese Verknüpfung gängen. Also, wir haben ja diese drei
Stufen, also Interface, abstrakte Klasse, konkrete Klasse.
Schauen wir mal ganz kurz, ob die obere Verbindung in Ordnung ist.
Die haben wir gemacht, ne? Also dieses Implements Expression
sorgt dafür, dass es diese Verbindung gibt von der abstrakten
Klasse zum Interface
Was fehlt denn jetzt hier noch? Wie lange nochmal jemand,
der heute noch nichts gesagt hat? Wie kann ich sagen,
dass also die konkrete Klasse A, ja,
diese beiden Implementationen aus dieser Simple Expression übernehmen soll?
Spannend für eine Idee, kriegt das jemand hin?
Ja, du hattest schon, ja. Genau,
Klaus A, Extense, Simple Expression, toll.
Jetzt beschwert sich der Compiler nicht mehr.
Dann kopieren wir uns das doch mal, ob es,
tun wir es mal hierhin.
Und dann tun wir das nochmal hier
Ja. Okay. Jetzt kann ich ja gucken,
ob die Tests noch durchlaufen. Ich bin total stolz,
auch wenn ich, dass ich den Test geschrieben habe.
Wir können sehen, dass wir nichts kaputt gemacht haben.
Ja. Walken ist grün, das funktioniert alles noch.
Ich bin beruhigt. Gut. Ja, das ist jetzt
im Prinzip dasselbe, was wir eben auch gemacht haben.
Nur, wir hatten jetzt nicht zwei Klassen, sondern drei.
Und wir hatten nicht eine Methode, sondern zwei.
Wir sehen, das funktioniert genauso gut.
Wir schauen uns die Super Expression nochmal ganz kurz an.
Das sind also diese beiden Methoden,
die nach unten vererbt werden in die Klassen A,
B und Not
Gut, jetzt hattet ihr ja noch gar nicht die Logik verstanden,
was die jetzt in den anderen beiden Klassen passiert.
Jetzt gehen wir so wieder zurück von dem eigentlichen Konzept der
heutigen Vorlesung zu meinen kleinen Spielereien.
Wo ist denn mal das And? Hier. Da schauen wir
uns das mal in Ruhe an. Vielleicht erstmal das hier
, ja? Also ich habe gesagt, die Präzedenz von And ist
10. Ich hatte erst eins und dann dachte ich so,
naja, wenn ich eine Präzedenz Null habe und eine Präzedenz 1,
was mache ich denn jetzt, wenn ich irgendwann einen Operator,
ob der dazwischen ist?
Ich kann ja nicht 0,5 nehmen. Also habe ich
mal lieber 10 genommen. Dann habe ich platzt dazwischen,
falls irgendwann mal weitere Operatoren dazukommen.
Ihr seht, dass sich diese zehn auch zum Beispiel hier übergebe,
wenn ich also meine beiden Operanten mit und verknüpfen will,
ja, dann stecke ich meine eigene Präsidenz rein,
damit ich was Sinnvolles zurückbekomme,
etwas Geeignetes für die Präzedenzstufe zehn, nämlich für das Rund.
Okay, so und jetzt, jetzt wird es spannend,
jetzt kommt der Code, der sich darum kümmert,
muss in Klammern generiert werden oder nicht.
Wollen wir uns mal ganz kurz zusammen anschauen? Also,
es kommt jetzt von außen die Anfrage, hey,
liebes End, gib mir doch mal eine lesbare Infexform.
Und ich verrate dir, was meine eigene Präzedenz ist von außen,
wenn das jetzt zum Beispiel eine Negation ist,
wird da null reinkommen.
An, es ist null. Dann fragen wir, ist null kleiner als zehn?
Ja, das kriegen wir hin. Wir sind ja,
haben ja alle einen Hochschulabschluss, null ist kleiner als zehn.
Dann müssen wir diese Klammern generieren. Und dazwischen die Infixform reintun.
Wenn es nicht kleiner ist als zehn, was könnte es dann sein?
Es könnte zum Beispiel zehn sein. Ja, dann hat uns
ein anderes End aufgerufen, dann brauchen wir keine Klammern.
Oh, das könnte 20 sein. Dann hat uns
ein Ohr aufgerufen, dann brauchen wir auch keine Klammern.
Das ist eigentlich so die Kernlogik, die Entscheidung,
auf wir Klammern brauchen oder nicht.
Ist das irgendwie klar? Können sich mal alle Leute melden,
denen so halbwegs klar ist,
was hier passiert mit der Präzedenz und dem Vergleich?
Ja,
okay, gut
Und das vergleiche ich jetzt mal mit dem Code,
der in dem Ohr steht. Morgens den Code auch
noch mal kurz angucken. Ich finde es immer toll,
wenn man sich Code anguckt und in einer anderen Klasse sieht er kurz so ähnlich aus,
dann ist man gleich ein bisschen weniger gestresst.
Denkt man, ah, alles klar, kenne ich ja schon.
Ihr seht, also hier wird einfach statt der 10,
die 20 reingesteckt. Meine eigene Präzende, es ist 20 statt 10.
Und ich vergleiche nur 20 stationen. Ansonsten ist es exakt dasselbe
. Ja? Das wäre eigentlich nicht eine Explar-Pariatorin gefahren.
Da könnte man dann weiterfallen und dann einfach auf die Inflar-Pariate
zu tragen
Also die Frage ist, warum wir die Präzedenz nicht an
eine Exemplarvariapple tun? Ich habe es einfach deswegen gemacht,
weil die Präzedenz ja eine Eigenschaft der Klasse ist und
nicht eine Eigenschaft eines Objekts sehr klasse.
Also alle Ans haben die Präzedenz 10 und das wäre,
jetzt kommt wieder frett mit seinem,
damals hatte der jetzt Commodore 64 nur 64 Kilobyte.
Es wäre ja Speicherverschwendung, diese 20 in allen Exemplaren der Klasse
Ans zu speichern, wenn es immer derselbe Wert ist.
Deswegen, ich habe einfach keinen Sinn darin gesehen,
diese 20 explizit abzuspeichern in jedem Exemplar der Klasse.
Aber das wäre dann halt auch einfacher zu ändern.
Da könnte man auch viel einfacher machen oder etwas beinehmen
Einmal für die Eltern, wenn man es raucht, hier ändern will.
Okay, du gehst schon in die Richtung, in die
ich gehen will. Es ist ja irgendwie blöd,
dass diese 20 an vier verschiedenen Stellen steht.
Weil dann ist es ja nicht exakt derselbe Code wie vorher.
Das ist ja doof. Okay. Und hier ist es
doch genauso. Die 10 stehen einmal hier,
einmal hier, einmal hier und einmal hier.
Wie oft müsste denn die 10 in diesem Quelltext stehen und
wie oft müsste denn diese 20 in diesem Quelltext stehen?
Also ich sag mal, mindestens. Kann ich sie
komplett entfernen? Das ist der beste Gott von Konte
Ich habe mir zugefallen. Okay, ich glaube,
akustisch habe ich jetzt dich nicht so verstanden.
Einmal eine Szenario. Einmal eine Szenario. Aber hier ist
es ja wirklich jedes Mal die exakt dieselbe Szene.
Also wenn ich mich irgendwann entscheide, hey, ich will
die zwölf Stati zählen und dann muss ich das ja überall ändern.
Okay, aber euch ist hoffentlich klar,
dass an einer Stelle muss auf jeden Fall diese Szene und diese 20 stehen,
sonst weiß ich ja nicht, was die Präsident ist.
Welche dieser zwanzigen wollt ihr behalten? Wollt ihr die an
der Stelle behalten oder an der oder an der oder an der?
Was wäre sinnvoll
Vielleicht mal, ja. In der Methode Präzedenz. Das ist
irgendwie logisch, oder? Ich will ja nach wie vor
eine Expression fragen können, was ist denn deine Präzedenz?
Also ich will, dass nur an dieser Stelle
die 20 steht und an allen anderen nicht.
Wie kann ich denn jetzt erreichen, dass ich hier die 20 wegkriege?
Ja. Wir rufen einfach die Methode auf. Ich frage
mich einfach selber, was ist denn meine eigene Präzedenz?
Kann ich ja machen. Vielleicht bin ich ja vergesslich.
Der Fred-Faktor. Okay. Das heißt, Jups, so
, ne? Und hier machen wir es nochmal genauso.
Der Sinn dieser Übung ist es also, zwei Ja,
Quelltexte zu nehmen, die fast gleich sind und sie exakt gleich zu machen sozusagen.
Das ist der Sinn der Übung, die wir hier gerade machen.
Das hat alles seinen Sinn. Denn wenn es exakt gleich ist,
dann kann ich ihn ja wieder hochziehen in eine abstrakte Oberklasse.
Ich habe wieder genauso. So, ne? Jetzt haben wir
also erreicht, dass hier und hier jeweils exakt dasselbe steht
wie in der Bruder- oder Schwesterklasse.
Das können wir also problemlos sofort eigentlich hochziehen
Ich will es aber nicht sofort machen,
weil dann hätten wir wieder nur eigentlich das,
was wir jetzt schon zweimal gemacht haben,
ich will noch einen Schritt weiter gehen.
Bis jetzt haben wir ja immer nur Methoden hochgezogen.
Wir haben uns Methoden genommen, zehn Zeilen Code,
war überall gleich, haben wir hochgezogen.
Okay. Vielleicht ist er aber noch mehr gleich in diesen Klassen.
Wollen wir mal gucken, ob die vielleicht noch mehr gemeinsame Eigenschaften haben.
Ich klicke mal hier so ein bisschen. Fällt euch da
irgendwas auf? Hm, das ist ja spannend.
Was könnten wir denn noch hochziehen? Ah, irgendwie
auch anders nochmal. Ja, du hattest auch schon,
du hattest auch schon immer dieselbe, du hattest noch nichts.
Ein bisschen lauter, bitte. Die Variante, die Exemplarva gehabt,
ne? Ich könnte euch hier zum Beispiel dieses X hochziehen
und dieses Öffnen, das brauche ich ja an beiden.
Ja, gut. Und auch diese Ghetter,
wie wir sie manchmal nennen, oder diese Methoden,
die einfach nur diese Variablen zurückliefern, die kann man genauso hochziehen,
weil die sind ja auch genau gleich in beiden Klassen.
Toll. Wollen wir das mal machen? Also, probieren wir
uns das mal. Wie wollen wir das jetzt nennen?
Wir brauchen eine Oberklasse, die für das Band und für
das Ohr gleichermaßen geeignet ist. Ich nenne es einfach mal,
ja, wie nennen wir das denn mal?
Muss man nennen, Binary Expression. Also binär,
also weil es eben mit zwei Operanten zu tun hat
So. So, und dann sagen wir Implements Expression.
So, und jetzt kann ich meine Exemplar-Rappen da reinziehen.
Das ist toll. Okay. Ha, er beschwert sich.
The Blank, Final Field X, may not have been initialized.
Ha. Das ist jetzt ein sehr spannendes Phänomen.
Ich hatte fast ein Problem gesagt. Wir haben jetzt eine
abstrakte Klasse mit Exemplar-Variablen. Wir können keine Exemplare erzeugen von
dieser Klasse. Und jetzt beschwert sich der Compiler,
dass sich das hier nicht inditalisiert habe.
Sieht jemand ein Problem oder irgendwie was Interessantes
, ja
Kennt, wenn er zeitlich den Weg runter kann.
Er kann nicht erwarten, wie er dürfen werden. Okay,
du würdest dir jetzt also protected machen und einfach aus der Unterklasse initialisieren?
Ja. Das, das geht, glaube ich, nicht,
weil sie Final sind. Ich könnte das Final jetzt wegnehmen,
aber das will ich nicht. Hat jemand eine andere Idee,
wie ich das Private Final lassen kann und dafür sorgen kann,
dass sie initialisiert werden, das X und das Y?
Du hattest halt noch nichts. Wir brauchen einen Konstruktor.
Wer findet das komisch, dass wir ein Konstrukt in eine abstrakte Klasse reinschreiben?
Ja, ne? Das ist irgendwie strange,
weil wir kennen doch, dass ein Konstruktor,
wann wird der aufgerufen, wenn ich irgendwie New schreibe,
das geht jetzt hier ja gar nicht.
Ihr müsst mir jetzt einfach mal fünf Minuten lang glauben,
dass das Sinn macht und dann löse ich das Rätsel auf
Also ich kopiere mir jetzt einfach mal den Konzert,
den muss ich natürlich gleich umbenennen.
Die Klasse heißt ja ganz anders. Muss natürlich genauso heißen
wie die Klasse, ne? Binary Expression. So. Okay.
Guck mal, jetzt sind diese Warnungen schon mal weg.
Das ist auch nicht schlecht. Jetzt machen wir weiter im Takt.
Dann haben wir gesagt, diese Ghetter können ja auch hoch.
So ziehe ich also auch in die obere Klasse.
So, hatten wir noch was? Schau mal, das
war doch jetzt auch genauso. Ich kann auch hochgezogen werden
. Oh, toll, was da alles gleich ist.
So, und hatten wir noch was? Genau,
das Ding hier unten. Diese relativ komplizierte Logik,
also relativ jetzt vielleicht mit einem Ghetter,
Die können wir jetzt auch hier wunderbar hochziehen.
Da müssen wir sie nur noch einmal lesen und verstehen.
So, haben wir das jetzt gemacht? Schauen wir mal
. Das heißt, ich lösche jetzt mal ganz recode.
Das haben wir jetzt ja hochgezogen. Bom, bum,
bum. Das haben wir hochgezogen, das haben wir hochgezogen,
das haben wir auch hochgezogen. Es bleibt ja fast
gar nichts mehr übrig, das ist nicht toll.
So, und jetzt müssen wir hier natürlich sagen, Extenz-Binary-Expression.
Zom. Okay. Was haben wir jetzt für ein Problem?
Oh, das ist ja spannend. Implicit Superconstructor Binary Expression
ist undefined for Default Constructor, must defined in explicit constructor
Okay. Das ist interessant. Also,
wir haben hier oben einen Konstruktor. Wir haben gelernt,
Exemplar Varianten werden vererbt, Methoden werden vererbt.
Das Blöde ist jetzt, Konstruktoren werden nicht vererbt.
Also nur weil es einen Konstruktor gibt in der abstrakten,
in dieser abstrakten Klasse. Und ich erbe jetzt davon,
habe ich hier unten nicht diesen Konstrukt.
Es gibt hier also keinen Konstruktor, den X und Y
entgegennimmt. Das müssen wir von Hand programmieren. Gut,
mache ich das also. Public All Expression,
X Expression Y. Ja, aber was mache ich denn jetzt
in dem Konstruktor
Also noch mal kurz zurückgehen. Wir haben ja hier,
wir haben also gesagt, die Klasse Binary Expression,
die ist dafür verantwortlich, sich um diese Exemplarbarjappen zu kümmern.
Sie soll die auch initialisieren. Ja, und jetzt bin ich
in diesem Unterkonstrukt und weiß gar nicht, was ich machen soll.
Ah, ihr meldet euch beide. Ich weiß nicht,
wen ich heute weniger oft hatte. Einer von euch beiden
ist mir egal. Sehr gut, du hast gesagt,
wir müssen mit dem Super den Konstruktor von Binary Expression aufrufen.
Da sehen doch Eclipse uns das auch satt oder zumindest
was Ähnliches
Na, hm, ja. Okay, also Eclipse sagt,
pass auf Fred, du hast dir keinen super hingeschrieben.
Das ist so, als hättest du sowas geschrieben.
Du versuchst also ein super Konstrukt aufzusuchen, den es gar nicht gibt.
Deswegen muss ich ja das X und das Y explizit übergeben.
Okay. Gut. Ach, das ist so wenig Quelltext.
Ich glaube, ich kopiere mir den fast mal in die Klasse Ant.
Das geht einfacher, als da das mögliche zu löschen,
oder? So. Und dann muss es natürlich Hand heißen und
ich muss auch von der abstrakten Klasse erben.
Christians. So, ne? Okay,
was machen wir jetzt
Wir testen. Es ist noch ein Fehler, toll,
hoch ist ein Fehler.
Da hat es übernommen, die Strecke von Bruder.
Am Ehren. Ah, ich habe die Striche falsch.
Das müsst ihr den Test ja aufdecken.
Tatsache. Gut. Was sagt er mir denn hier?
Ihr könnt das Wort nicht lesen, ne?
Schau mal, ist das nicht halbwegs verständlich? Er hat
ein und und erwartet, aber hat stattdessen ein oder oder gefunden.
Ja. Okay, also das ist also in meinem End,
wo ist die Klasse End
Ach so, Moment, nee,
das ist jetzt ja ein Problem in der abstrakten Klasse.
Oh, haben wir da zu viel hochgezogen? Ha,
das ist ja blöd. Damit habe ich ja gar nicht gerechnet.
Okay. Wollen wir vielleicht nochmal auf Guidos-Folien zurückgehen?
Die am Ende kamen mit dieser Schablonen-Methode und der Einstrug-Methode.
Also was wollen wir denn hier eigentlich generieren?
Wir wollen ja einen geeigneten Operator irgendwie hier hinschreiben, oder?
Wir wissen aber nicht, was der Operator ist.
Wir wissen nur, dass es ein String ist.
Also kann ich doch vielleicht mir einfach wünschen,
in der Informatik wünscht man sich ganz oft, dass gewisse Dinge existieren.
Ich wünsche mir einfach, dass es sowas gibt wie Operator.
Der Operator soll da hingeschrieben werden. Gibt es jetzt natürlich
nicht. Was mache ich denn da? Lass ich
mich mal generieren. Und der Trick ist jetzt folgender.
Ich mache die Methode jetzt mal. Moment, schauen wir
zuerst protected oder zuerst abstract? Ist, glaube ich,
egal, oder? Stimmt. So. Ich sage also,
so die Grundform, wie man so ein Binary Expression zusammenstopft,
ist eigentlich klar. Ich sage erstmal dem einen Operanten,
dass er sich schön darstellen soll und dem anderen auch.
Und dazwischen muss eben ein schöner Operator kommt.
Welcher das ist, kann ich aber nicht wissen.
Dafür bin ich zu abstrakt. Das heißt,
wir gehen jetzt mal hin in unser End und in
unser Ohr und implementieren das hier jetzt mal
Und geben dann die entsprechenden schönen Strings zurück.
Also wie sieht der Operator aus? Der sieht ja so
aus. Und wie sieht er in Ohr aus?
Da sieht er, ja. So, also. Das
testen wir nochmal. Oh, immer noch irgendwas falsch.
Okay. Was ist hier das Problem? Güte. Oh
, ich habe die Präzedenz nicht geändert
Also gehe ich mal zu meinem End,
wo ist mein End hier? Tatsache, vielen Dank.
So, Tests sind toll, muss ich schon sagen.
Und dass ihr dabei seid, finde ich auch super,
weil so schnell wäre mir das jetzt nicht aufgefallen.
Glückwunsch oder nein? Toll, dass ihr so mir gut
geholfen habt. Okay, ja, habt ihr zu dem,
was wir jetzt gerade gemacht haben, noch fragen?
Ich habe nämlich noch so ein, zwei kleine Schmankerl,
die würde ich noch gerne mit euch machen im Zusammenhang mit Vererbung.
Nee? Okay, das sind so die Basics von Vererbung,
ja, gemeinsamen Code irgendwie hochziehen.
Okay, jetzt habe ich noch so ein paar Sachen vorbereitet,
bitte bleibt noch ein bisschen ruhig.
Das haben wir gemacht, dafür haben wir keine Zeit mehr
, genau
So, jetzt, genau, jetzt schließe ich immer alles und versuche mal
irgendwie alles so darzustellen, dass ihr alles irgendwie seht.
Also, ich zeige euch jetzt ein bisschen Quelltext,
den schaut ihr euch an, kann man das immer kleiner machen?
Ja, wir müssen, wir müssen uns das einmal uns kurz zusammen angucken.
Eine ganz einfache Klasse. Ich führe euch hört hier Klasse durch,
super einfach. Also, wir haben nächsten Plava gehabt.
Okay. Wir haben einen Konstruktor,
wo ich einen Start werde für diese Echsen-Favariable reintue.
Auch einfach. Ich habe eine Methode,
wo diese Variable um einen erhöht wird, auch einfach.
Ich habe einen Ghetter, wo der Wert zurückgegeben wird,
auch einfach
Ich habe hier diese spezielle Methode To-String,
wo Guido euch heute gesagt hat,
die kommt aus der Klasse Opt-Chat und man kann sie entsprechend ersetzen.
Die habe ich immer so überschrieben,
dass irgendwie so schön dieser Wert in so Spitzeklammern geschrieben wird.
Ja, das sieht einfach toll aus.
Okay, das ist doch eine einfache Klasse, oder?
So, und jetzt Thema Vererbung. Jetzt habe ich eine
versucht, eine Klasse zu schreiben, die davon erbt.
String-Counter soll also von Counter erben. Irgendwie wird noch ein String
hinzugefügt. Ja, ich wollte noch so einen String haben.
Und dieser String wird eben auch in diese Ausgabe reingeneriert.
Das ist einfach so eine leicht technisch motiviertes Beispiel.
Okay. Das lässt sich jetzt nicht übersetzen. Und wenn
ihr aufgepasst habt, Die letzten zehn Minuten müsstet ihr eigentlich verstehen,
warum ich das nicht übersetzen kann?
Sieht jemand, warum das nicht übersetzbar ist?
Der Compiler beschwert sich beim Konstruktor, der Klasse String Counter.
Ja, immer dieselben. Ja, immer dieselben.
Du hattest am wenigsten heute gesagt. Weil es kein Default
Zuckerkonstruktor gibt. Er sagt mir an der Stelle,
wenn du mir nicht sagst, welchen Superkonstruktor ich aufrufen soll,
dann nehme ich den Default-Konstruktor und den gibt es nicht.
Schauen wir nochmal, dass er das wirklich sagt.
Im Clicit Superkonstruktor, bla bla bla, okay.
Ich muss also irgendwie hier ein Superkonstrukt aufrufen und ich übergebe
einfach mal null.
Der Counter beginnt bei null.
So, jetzt ist die zweite Frage. Was passiert,
wenn ich das starte? Dazu muss ich euch natürlich den
Klienten zeigen. Wie mache ich das denn mal so,
dass ihr jetzt alles gleichzeitig seht?
Kann ich das nicht irgendwie so machen oder so?
Was ist da sinnvoll? Machen wir es mal so.
So, ja, könnt ihr alles gleichzeitig sehen.
Ich hoffe schon. Also, das ist also dieser String-Counter
, mache ich mal vielleicht so.
Und das ist der Klient, den mache ich mal so.
Ja, es ist alles knapp hier.
Könnt ihr beides lesen? Ich hoffe ja, ne?
So. Und jetzt sollt ihr einfach mal im Kopf durchgehen mit
eurem Wissen über Vererbung und was soll alles passiert?
Was denn da wohl rauskommt auf der Konsole?
Also ich führe euch wieder durch den Gefälltext.
Wir erzeugen uns also ein Exemplar dieser Klasse String-Counter,
die seht ihr ja hier
Ich stecke Hello als Parameter rein. Diese Variante hat also
den Wert Hello. Dann führe ich dreimal das Inkrement aus.
Und dann tue ich diesen Counter auf die Konsole schreiben.
Und wenn ich so ein Counter auf die Konsole schreibe,
dann wird ja hier dieses True String aufgerufen.
Ich stecke also Hello rein, rufe dreimal Inkrement auf und
sage dann, schreib dich doch mal auf die Konsole.
Gucken wir uns das Schreiben auf die Konsole noch mal ganz kurz an.
Das wird True Upper Case gemacht und dann kommt dahinter der Wert.
Okay. Was kommt da raus? Hello 3,
das würde man denken
Und jetzt sind diese Pushler immer so,
dass sie genau nicht das tun, was man denkt.
Okay, also führen wir es mal aus. So,
was ist jetzt dabei rausgekommen? Wow. Ah,
nein, ich habe ja einen Zwischenschritt noch vergessen.
Es kommt genau das raus und jetzt will ich was machen.
Sorry, sorry, meine Schuld. Und zwar möchte ich jetzt,
wenn dieser Konstruktor durchgelaufen ist, kommt irgendwann ein,
kommt der Auto dieser Klasse auf die Idee, hey,
ich will irgendwie wissen, dass der Konstruktor erfolgreich durchgelaufen ist.
Ich sage jetzt einfach mal Systomer Printline, Initialized
This successfully. Ich mache jetzt mal so,
dass ihr wieder hier alles seht.
Das Main braucht ihr, glaube ich, nicht mehr.
Das hat sich ja nicht geändert. So. Also der Autor
der Klasse ist entschieden. Ich will, dass das irgendwie
gelockt wird, dass dieses Exemplar vernünftig erzeugt wurde.
Okay. Ändert sich dadurch irgendwas? Das ist jetzt
das einzige, was ich ändere. Ich verspreche euch,
jetzt kommt was anderes aus. Die Spannung ist ins Unermessliche gestiegen.
Soll ich es einfach mal starten? Ist das spannend?
Ich hoffe, es wird jetzt passiert mal was.
Oh ja. Oh, Wahnsinn. Was ist denn hier
los? Ein Highpointer-Acception. Okay
Das ist ja merkwürdig, was denn hier passiert.
Wieso bin ich da jetzt in dieser Methode drin gelandet?
Ich habe doch gesagt, Counter,
gib doch mal This auf die Konsole auf und dabei wird ja
True String aufgerufen und True String habe ich doch so implementiert.
Müssen wir da nicht einfach nur stehen, initialized,
null, successfully? Wieso kommt denn Dyson Nightmare to Acception?
Ja. Wir machen ein String Counter. Ja,
das heißt,
wir berühmten
Er hat Probleme mit This persönlich, persönliche Probleme, ja?
Das ist das eine, das Abjekt von ihm vertraut hat.
Wir haben ja gar keinen Amt hier von mir gekauft.
Wir haben eine Abjektung. Wir betonen auch,
wenn das halt einfach so war, so war das auch.
Ich würde mich sehr heilig erhalten,
dass es ein Objekt ist, das genau da ist,
das böse Antwort werden, das hat jeweilige Gemeinde.
Die spannende Frage ist, was ist eigentlich der dynamische Typ
von This? Was ist der dynamische Typ von This
in diesem Konstruktor
Ja. Ja, ja, ja, ja, ja, ja.
Ja, dann einfach war zu 90 Prozent richtig.
Also alles, was du nach den ersten zwei Sekunden gesagt
hast, war richtig. Das Blöde ist in Java,
dass der dynamische Typ eines Objekts sich nie ändert.
Also, wir kommen ursprünglich, kommen wir ja von hier
aus diesem Konstruktor, ne? Gut, das heißt,
wir sind irgendwie dabei, ein Stream-Counter-Objekt zu initialisieren.
Und selbst wenn wir jetzt sagen, okay,
dazu muss ich erstmal dieses Counter-Objekt industrialisieren,
der dynamische Typ von This
ist trotzdem String-Counter
Ja, und deswegen ist es in den meisten Fällen ein
riesiges Problem aus einem Konstruktor, Methoden aufzurufen, die man überschreiben könnte.
Das ist so eine der Lektionen, die man mit der Zeit irgendwann lernt.
Meistens fliegen einem dann solche Exception um die Ohren.
Das finde ich irgendwie total krass. Ich könnte diese Variablen
hier sogar Final machen. Das macht überhaupt keinen Unterschied.
Das heißt, ich kann diese Variablen zwei verschiedenen Zuständen beobachten.
Und wenn sie nall ist, dann knallt sie natürlich.
Tja, okay. Gut. Mit diesem Thema Vorsicht,
Vererbung ist ein kompliziertes Biest, entlasse ich euch.
Und nächste Woche wird es wieder ein bisschen gemäßigter