Ja, willkommen zur 30. Vorlesung von SE2. Bald ist es
geschafft. Nur noch diese und nächste Woche. In der
nächsten Woche werde ich wahrscheinlich inhaltlich nur eine halbe Vorlesung machen,
ein bisschen retrospektive halten. Und dann werden wir uns hoffentlich
die Ergebnisse der Lehre-Evolution angucken können. Das mache ich eigentlich
immer ganz gerne der letzten Vorlesung in der zweiten Hälfte.
Aber heute geht es nochmal ein bisschen um Inhalte.
Und heute ist es auch mal wieder,
sagen wir,
etwas anspruchsvoller
Wir sind jetzt an einem Punkt, wo wir
relativ viele Konzepte, Softwaretechnischer Art euch näher gebracht haben.
Und jetzt wollen wir uns so ein bisschen angucken,
was da noch so dazukommt, was da noch mehr kommt.
Also so ein bisschen jenseits von dem,
was man so praktisch tut, sehen wir uns heute an.
Zum einen in der ersten Hälfte,
wie sieht das mit der Formalisierung von Programmiersprachen aus?
Wir sind ja hier an einer Uni und beschäftigen uns dann
auch gern damit. Wie können wir denn nachweisen,
dass Programmiersprachen auch das tun, was sie versprechen,
zu tun?
Wie kriegt man
das formaler
Dargestellt vom Maler, verarbeitbar. Das ist so die
erste Hälfte bis zur Pause und nach der Pause werden wir
uns dann metasprachliche Aspekte der Programmierung angucken.
Also unter anderem das Meta-Project-Protokoll von Java. Okay,
jetzt also in der ersten Hälfte geht es um formale Korrektheit.
Und um letztendlich abstrakte Datentypen,
die so einen sehr praktisch relevantes Konzept sind,
was in dem Versuch entstanden ist,
die Konzepte von Programmiersprachen formaler
zu fassen
Ich motiviere das so ein bisschen über Softwarequalität,
habe ich ja schon ein paar Mal gemacht.
Diese Vorlesung ist klassisch eine Vorlesung im zweiten Termin gewesen,
in SE2 und hat da überhaupt nicht hingepasst,
weil irgendwie so viele Konzepte nicht klar waren und es war so abstrakt,
dass wir gesagt haben, wir ziehen das nach hinten.
Also jetzt heute und diese Titelfolie ist noch,
sagen wir, nicht richtig überarbeitet worden von mir.
Wir lernen das Vertragsmodell, also nicht heute Kinder,
das haben wir schon in Vorlesung 2 kennengelernt, weil das ist da geblieben,
weil das eben für unsere praktischen Übungen auch sehr relevant war.
Und wir sehen heute so ein bisschen,
wie es zum Vertragsmodell gekommen ist
Das schauen wir uns zumindest in Ausschnitten an. Die üblichen Verdächtigen,
Bertrand Meier natürlich mit seinem Object Oriented Software Construction als Literaturhinweis,
dann gibt es hier so ein bisschen was auch zu grundlegenden Begriffen,
also hier im Handbuch von Rechenberg und Pomberger und etwas über Algorithmen und Datenstrukturen,
wo auch diese abstrakten Datentypen ziemlich prominent beschrieben sind.
Ja, jetzt nochmal Zuverlässigkeit als Merkmal für Softwarequalität.
Wir haben schon ein paar Mal gesagt,
es geht hier in der Veranstaltung nicht einfach nur ums nackte Programmieren,
sondern auch darum, nachhaltig Software zu entwickeln.
Und da spielt die Softwarequalität eine wichtige Rolle.
Und dann natürlich auch Zuverlässigkeit. Also ich möchte natürlich,
möchte jeder von uns zuverlässige Software,
wir wollen auch zuverlässige Menschen in unserer Umgebung haben,
wenn wir uns über Menschen unterhalten, die zuverlässig sind,
dann sagen wir, ja, das ist wichtig,
dass er sich korrekt verhält.
Also wenn ich so einen Bankangestellten sehe,
der soll natürlich möglichst korrekt arbeiten, das finde ich gut.
Schön ist aber auch, wenn Personen,
auf die ich mich verlassen will, die zuverlässig sein sollen,
auch robust sind, also auch irgendwie damit umgehen können,
wenn mal was nicht so gut läuft.
Also das ist durchaus ergänzend zu sehen.
Und das ist bei Software halt genauso.
Also zum einen geht es darum, Wann ist Software korrekt?
Das kann man nur beurteilen in Bezug zu einer Spezifikation.
Das habe ich schon angedeutet. Und Robustheit ist dann,
was passiert denn, wenn man die Nicht-Spezifikationsgemäß verwendet, die Software?
Wie geht sie damit um? Stützt sie da gnadenlos ab
oder hat sie noch eine sinnvolle Fehlermeldung und ähnliches?
Nochmal einfach zur Wiederholung. Dieser Korrektheitsbegriff ist ja nicht
so ganz einfach, aber er ist eigentlich relativ klar definiert.
Korrekt ist Software immer dann, wenn sie ihre Spezifikation erfüllt.
Das ist nicht leicht, das nachzuweisen,
dass ein Stück Software
eine Spezifikation erfüllt
Insbesondere ist erst erst dann möglich, wenn die Spezifikation formal ist.
Wenn ich hier die Spezifikation habe, hier die Implementation,
die diese Spezifikation erfüllen soll, ich müsste einmal diesen Ableitungsprozess,
also irgendwie das erfüllt das,
das musste ich formal machen können.
Dafür muss das auch formal genug sein, dass ich mich darauf beziehen kann.
Das heißt also, man kann dann über formale Spezifikationen nachdenken.
Wir haben hier schon mehrfach deutlich gesagt,
dass in der Praxis der Softwareentwicklung häufig nicht der Fall ist,
dass man das vollständig formal spezifiziert.
Das ist fast auch fast nie möglich. Es gibt Bereiche,
in denen das möglich ist, natürlich.
Chipdesign zum Beispiel
Aber auch da ist natürlich immer die Frage,
muss man sich klarmachen, selbst wenn ich jetzt das kann,
ich habe eine formale Spezifikationstechnik, ich kann irgendwie nachweisen,
dass die Implementation, die Spezifikation erfüllt,
dann habe ich immer noch nicht die Garantie,
dass die Spezifikation selbst korrekt ist.
Also das ist sozusagen so das Richtige tut,
also valide ist im Sinne der Begriffe,
die wir hier in Vollensung 1 eingeführt haben.
Und für interaktive Programme umfangreiche Art ist das üblicherweise eigentlich nicht
anwendbar,
da formale Korrektheitsbeweise durchzuführen
Auch nochmal so ein bisschen diese Begriffe Korrektheit und Robussheit ein bisschen nochmal
in die Verhältnisse zu rücken, wie wir sie hier behandeln.
Ihr erinnert euch, wir legen euch nahe,
möglichst Testfälle zu schreiben, also Test ausführbare Testfälle zu schreiben,
Testklassen, die eure fachlichen Klassen testen, eure fachlichen Schnittstellen testen.
Und üblicherweise schreibt ihr dabei Testfälle und ein Testfall besteht immer
aus erwarteten Daten und einem Vergleich mit dem, was dann tatsächlich beim Aufruf rauskommt.
Also immer so der Abgleich,
ich erwarte jetzt eigentlich das und es kommt das raus und
das wird verglichen und dann gibt es irgendwie eine Fehlermeldung oder nicht
Und wir haben unterschieden beim Testen zwischen positivem und negativem Testen. Und ich weiß nicht,
ob das bei allen so ganz sattelfest ist,
diese Begrifflichkeit, aber es ist einfach so,
wenn ich spezifikationskonform teste, also wirklich die Spezifikation sagt,
es gibt die fünf Operationen mit den Parametern,
dann rufe ich genau diese fünf Operationen auf,
genauso wie es gedacht ist und sehe halt,
ob das Ding sich korrekt verhält,
spezifikationsgemäß.
Also positive Tests erhöhen das Vertrauen in die Korrektheit.
Je mehr ich davon schreibe, desto sicherer bin ich mir,
ich bin mir nie hundertprozentig sicher,
haben wir auch gelernt,
man kann mit testen,
nicht die Korrektheit von Software nachweisen
Man kann nur die Anwesenheit von Fehlern nachweisen. Wenn wir aber negativ testen,
also tatsächlich Dinge testen, die man nicht tun sollte,
also wenn in der Spezifikation steht, von einer Operation,
hier dürfen nur Werte kleiner tausend übergeben werden.
Na dann rufe ich trotzdem erst mal auf,
mit einem Wert größer tausend, um zu sehen,
wenn alles zusammenbricht oder nicht.
Also dieses an der Spezifikation vorbei testen, über die Spezifikation hinaus testen.
Das ist negatives Testen. Und wenn man die macht,
dann erhöht das halt üblicherweise das Vertrauen in die Robustheit,
also dass die Software
nicht gnadenlos abschmiert,
wenn sowas passiert
Genau, also das sind so diese Begriffe,
Korrektheit und Robustheit nochmal so ein bisschen wiederholt.
Und dann ist hier so ein bisschen wieder mal die Wiederverwendung
oder Wiederverwendbarkeit von Software im Vordergrund. Insbesondere dann,
wenn ich Software wiederverwenden will, wenn ich einen Framework verwenden will,
Bibliotheksklassen oder ähnliches, dann ist es mir natürlich schon wichtig,
dass das zuverlässig ist, was ich da wiederverwenden will.
Und dafür muss ich eben halt sehen, dass es verständlich ist.
Und dass ich erstmal weiß,
was es tut und mir aber auch sicher sein kann,
dass es das wirklich richtig tut, dass es eben tatsächlich zuverlässig ist.
Und was wir uns hier ja schon intensiv angesehen haben,
sind die Interfaces aus Java. Die Möglichkeit,
explizit mit einem Sprachmechanismus die Schnittstelle einer Klasse zu beschreiben.
Wunderbar. Und das ist aber, wie wir gelernt haben,
sozusagen diese pure Objekte und hinter die Sicht,
da ist es halt eigentlich nur mal den Klassennamen oder
den Typennamen von einem Interface und die Signaturen von Operationen,
ohne dass irgendwelche Implementationen dazu angegeben sind.
Es fehlt eigentlich die Spezifikation der Semantik. Das heißt,
wenn ich eine Schnittstelle euch gebe und sage, hier implementiert die mal,
dann muss ich eigentlich mit den Armen wedeln oder natürlich sprachlichen Text dazu schreiben,
damit ihr wisst, was zu tun ist.
Also, das Beispiel kennt ihr schon, habe ich schon mal gezeigt.
Ich habe hier Interface Stack mit Operation Push Pop, ist MT.
Ich habe ein Interface Q mit den Operationen 1Q,
DQ und is empty und ein Interface ergänzend Geblubber mit den Operationen Blablö und Blub.
Die sind syntaktisch gleich. Die unterscheiden sich eigentlich nur in
hier diesen Namen. Ansonsten ist es das Gleiche.
Die Parametertypen sind gleich, die Ergebnistypen sind gleich.
Das heißt, rein syntaktisch ist hier eigentlich nichts darüber gesagt,
was diese Dinge tun
Bei einem Stack würden wir erwarten, wir haben natürlich, wir haben Kontextwissen.
Wir wissen, wie ein Stack funktioniert. Also würden wir sagen,
naja, wenn ich Push aufrufe, dann kommt da irgendwie
auf so ein Stack was drauf und dann push ich,
rufe ich nochmal Push auf, dann kommt das Element da drauf.
Wir haben wieder Pop-Aufrufe, das Ober, das letzte,
zuletzt eingefügte Element entnommen. Also so eine Last-in-First-Out-Strategie. Würden
wir erwarten. Und bei einer Q ist es irgendwie anders.
Also es ist eine FIFO-Strategie. Und bei Geblubber wüsste ich
jetzt gar nicht, wie da die Semantik sein soll.
Es ist relativ schwierig zu erkennen. Aber syntaktisch sind die
gleich. Ihr wisst, in Java ist die Signatur
einer Methode bzw. Operation definiert auch mit dem Namen.
Also ist der Name hier eigentlich Teil der Semantik in Java.
Aber es geht ja erstmal darum, dass euch klar ist,
dass das nur semi-formale Spezifikationen sind.
Wenn ihr wirklich jetzt so eine Spezifikation schreiben wollt,
dass jemand anders in ganz woanders auf diesem Planeten diese Spezifikationen implementieren kann,
auf Basis der Spezifikation, dann muss schon ziemlich viel Informationen dazukommen,
wie denn diese Spezifikation zu implementieren ist.
Da kann man nicht sagen, naja, jeder weiß ja,
wie ein Stack funktioniert, mach mal.
So, dann muss man halt irgendwie sich überlegen,
wie das besser geht
Insgesamt ist das ein relativ breites Thema.
Das ist jetzt hier so ein bisschen noch fokussiert auf OO-Schwierigkeiten.
Das ist schon sehr, sehr lange Thema in der Informatik.
Wie kriegen wir denn die Semantik von Programmen irgendwie genauer gefasst?
Wie kriegen wir es hin? Klarer, möglichst formal,
mathematisch formal zu definieren, was denn so ein Softwaresystem macht.
Und da gibt es eben halt verschiedene Ansätze der formalen Semantik von Programmen.
Und man möchte das machen, um eben halt Verhalten auch
statisch beweisen zu können. Also wirklich sagen zu können,
ja, das habe ich jetzt mir so angeguckt,
das macht garantiert genau das,
diese Software
Wenn man dann das auch noch für alle gültigen Eingaben eines Programms
zeigen kann, dann ist der Nachweis eben halt erbracht.
Das ist so dieses Thema Verifikation. Da werden,
wie gesagt, üblicherweise ausschließlich mathematische Methoden verwendet. Das heißt,
man versucht irgendwie die Computerprogramme sind taktisch herzunehmen und dann daraus
irgendwie Semantik abzuleiten, also irgendwie Ableitungsregeln und Kalküle anzuwenden.
Um Aussagen über Programme beweisen zu können. Das ist,
wenn man so will, jetzt so den Übergang zur
theoretischen Informatik und das ist auch nicht der Bereich,
in dem ich mich sehr gut auskenne.
Ich werde jetzt nur so kurz anreisen,
was so klassische Vertreter sind in diesem Bereich,
also wie man formale Semantik von Programmiersprachen beschreiben kann.
Der allgemeinste Ansatz ist eigentlich die denotationelle Semantik.
Die geht eigentlich nur davon aus, was im Speicher passiert.
Also ein Programm macht ja irgendwas, in dem es Speicherzellen verändert.
Man könnte also auch platt sagen, Semantik ist das,
was im Speicher passiert.
Und die machen das eben bei der die notationellen Semantik so,
dass sie sagen, wir sehen den Zustandsraum,
also die Menge aller möglichen Speicherzustände,
das sei
Bilden ab, ein Programm hat einen Speicherzustand zu Anfang und
bildet dann ab auf einen anderen Speicherzustand. Das heißt,
das Programm ist eine Abbildung, eine Funktion,
die von dem einen Zustand in den anderen abbildet,
auf Basis von Anweisungen, eine Anweisung A oder eines Programmes A.
So, und wie das dann im Detail funktioniert,
ist ein ganz anderes Thema, aber da ist es tatsächlich so,
die sind eigentlich unabhängig davon, welche Programmiersprache, welches Programmiersprachenmodell,
das ist einfach,
das ist wirklich sozusagen die reinste Form der Semantik-Definition,
Speicherzustand auf Speicherzustand und
da irgendwie das in mathematische Begriffe fassen zu können
Bei der operationalen Semantik ist das ein bisschen anders.
Da versucht man, das, was da passiert,
zu beschreiben mit Hilfe von einer Maschine. Das heißt,
man hat irgendwie eine Maschine, üblicherweise irgendeine formale Maschine,
die etwas tut und sagt dann, das,
was unser Programm tut oder unsere Programmiersprache macht,
das ist so ähnlich wie das, was auf der formalen Maschine passiert.
Üblicherweise nimmt man als formale Maschine da die Turing-Maschine,
die ihr vielleicht schon kennt, aus FGI 1,
um irgendwie das zu beschreiben, was in einem Computer so passiert.
Das heißt also, das ist dann nicht mehr
nur der Speicher, sondern auch ein Modell einer Maschine,
die auf diesem Speicher arbeitet
Da muss man schon mehr sozusagen hinzunehmen als Grundvoraussetzung.
So ähnlich ist auch die Translationale Semantik, also auch Übersetzersemantik genannt.
Das heißt, wenn ich für eine Programmiersprache definieren will,
was eine bestimmte Anweisung tut, dann sage ich, naja,
ein Four-Each oder sowas in irgendeiner Sprache mit so ein paar Variablen,
wird abgebildet auf mehrere Maschinen-Curt-Anweisungen auf eine andere Maschine.
Also ich kann zum Beispiel abbilden auf die Java-Vulture-Machine. Dann
habe ich tatsächlich eine Sprache und eine andere Sprache und bilde sozusagen meine neue Sprache,
um die Semantik zu definieren, ab,
indem ich sage,
dieses Konstrukt ist in der Sprache
das und das
Dort noch mehr Kontext wissen, man braucht eben halt Speicher,
man braucht eine Maschine und man braucht eigentlich auch eine Sprache,
die da irgendwie auf dieser Maschine dann läuft.
Okay, und sagen wir, die am kleinsten granulare Form
ist diese Idee der axtomatischen Semantik und der hat auch,
deswegen werde ich ihn jetzt ein bisschen ein bisschen genauer beleuchten,
am meisten Bedeutung für die Objektorientierung.
Da ist es so,
dass man für die einzelnen Programmelemente Vor- und Nachbedingungen festlegt,
hat jetzt noch nichts mit den Vor- und Nachbedingungen zu tun,
die wir vom Vertragsmodell erkennen.
Das ist zwar davon inspiriert, aber erstmal hat das nichts damit zu tun.
Das heißt, man guckt sich In einem imperativen Programm,
sequenzielle Anweisungen an und sagt eigentlich bei jeder Anweisung, vorher gilt das.
Und wenn die Anweisung ausgeführt wird, gilt hinterher was anderes.
Der klassische Ansatz dazu ist, das ist der sogenannte Horr-Kalkül.
Der gute Herr Horn, ihr habt ja jetzt
schon an verschiedenen Stellen, glaube ich, kennengelernt.
Wir haben seinen Typbegriff hier ausführlich beleuchtet. Ihr habt
Quick-Sout implementiert, den hat der gute Herr Horr erfunden.
Wenn ihr irgendwann mal mit nebenläufiger Programmierung arbeitet und dann sowas
wie Monitore baut, dann die auch in Joe übrigens umgesetzt sind.
Die sind von Tony Horr und er hat auch auch das hier in die Welt gesetzt,
1969, sei da im Skript ein bisschen komisch verwurschtelt,
das ist also 69 soll das hier sein.
Diesen Horkalkül in die Welt gesetzt und den beleuchte ich nur relativ kurz. Da ist es so,
die Semantik einer imperativen Anweisung,
aber wir sind jetzt sozusagen schon sehr auf der kleinen granularen Ebene,
wir sehen also jetzt in einem imperativen Programm so eine Sequenz von Anweisungen,
gucken jetzt eine einzelne Anweisung an und sagen,
ich versuche die Semantik jetzt zu definieren über ein Triple,
nämlich eine Menge von Prädikaten, die Vorbedingungen sind,
die Anweisung ausgeführt.
Also es ist ein Triebel, Prädikate vorher, Prädikate hinterher.
Vorbedingungen nach Bedingungen. Wenn P vor der Ausführung von A gilt,
wenn die Vorbedingung gilt, dann muss Kuh nach der Ausführung von A gelten.
Das ist eben halt dann hier die Nachbedingung.
Das heißt, um mal ein ganz einfaches Beispiel herzunehmen,
und das ist, das Horc halt Kühl ist natürlich deutlich Figelinsche,
da muss man schon sehr genau hingucken, wenn man das,
also man kann da locker eine ganze Vorlesung drüber halten,
mache ich jetzt nicht.
Aber das ist sowas wie hier, ich habe so
eine Anweisung wie X ist gleich X plus 1.
In einer eperativen Sprache. So, was ist jetzt die Semantik davon? Naja,
das ist letztendlich hier Das, was man aussagen kann,
ist, dass zum Beispiel hier, wenn Folgendes gilt,
null, ist kleiner gleich X und das ist
kleiner gleich 1000. Also X hat einen Wert von 0 bis
9099. Wenn das vorher gilt und dann wird diese Anweisung ausgeführt,
dann gilt hinterher, dass X einen Wert hat von 1 bis
1000. Irgendeinen Wert auf den Bereich. Das sind also die
Dinge, die man aufgrund der Semantik dann ableiten kann.
Das, wie gesagt, kann man sehr intensiv weiter betrachten.
Unter anderem muss man natürlich auch hier dann sehen,
dass das natürlich nur funktioniert, wenn A terminiert,
also diese Anweisung auch wirklich terminiert, nicht irgendwie endlos läuft und so weiter.
Dieser Ansatz ist entstanden in der Hochphase imperativer Programmiersprachen.
Also so in den 60er und 70er Jahren waren ja die imperativen Programmiersprachen sehr angesagt.
In den 60er Jahren sind ziemlich viele Programmiersprachen entstanden.
C ist da entstanden. Pascal ist da eigentlich auch schon entstanden.
Allgäu, Algäu 68. Also diese ganzen klassischen imperativen Programmiersprachen,
also sowieso Prä-Objekt-orientiert quasi. Und da waren eben halt so
diese imperativen Konzepte starken Vordergrund. Also Anweisungen, Zuweisungen,
Sequenzen, also einfach Anweisungen, eine Nacheinander,
bedingte Anweisungen und Schleifen
Alles, was mit Schleifen zu tun hat. Also im
Horrorkalkül gibt es dann das Konzept von Schleifen in Varianten.
Das heißt, ich sage, vor Ausführungen der Schleife gilt etwas
und dann gilt etwas während der ganzen Ausführung der Schleife ständig, also eine Schleifen-in-Variante.
Und wie gesagt, da kann man sich sehr ausführlich mit beschäftigen.
Als ich vor, oha, dann habe ich angefangen zu studieren,
85. Mann, Mann, Mann,
das ist ja jetzt bald 30 Jahre her, schrecklich.
Da habe ich sowas noch gelernt. Ich habe tatsächlich noch irgendwie hier Horrkalkül,
also so Verifikationstechniken auf einzelnen imperativen Anweisungen gelernt in meinem Studium.
Gebraucht habe ich das danach eigentlich nicht mehr.
Jetzt könnte man sagen,
ist das denn wichtig
Und ich glaube, ja, dass es schon wichtig ist,
weil was ich dabei gelernt habe, ist,
dass ich mir sehr viel klarer Gedanken mache, darüber,
was in meinem Programm passiert.
Also wenn ich mir sehr formal ansehe,
was auf Anweisungsebene passiert und mir auch genau Gedanken mache,
Vorbedingungen, Nachbedingungen, dann habe ich einfach ein tieferes Verständnis von dem,
was beim Programmieren passiert.
Und das ist immer gut, wenn man mehr weiß,
wenn man ein besseres Verständnis hat, dann ist man auch ein besserer Programmierer.
Von daher ist es vielleicht eher so,
dass man diese formalen Ansätze als Verständnis bildend sehen sollte,
aber nicht unbedingt als direkt praktikabel einsetzbar
Insbesondere ist das natürlich entstanden, insbesondere für imperative Programme.
Das heißt, hier Anweisungsfolgen mit elementaren Datentypen üblicherweise und Koltorstrukturen,
die dann als partiell korrekt beweisbar waren.
Und für Objektentitelprogramme, wie wir sie ja nun hier mehr
oder weniger tagtäglich haben, ist das alles ein bisschen schwieriger.
Sobald man Referenzvariablen hat, die ihr ja kennt in Java,
die Referenzen, und auch sowas wie dynamisch gebundene Methodenaufrufe,
da wird das alles sehr, sehr kompliziert.
Also es gibt dann auch Ansätze, zum Beispiel die Formate,
normale Semantik von Java mal oft festzuhalten.
Da wird dann eine Untermenge von Java hergenommen. Da werden dann
die ganzen komplizierten Sachen rausgeschmissen, damit man irgendwie so einen Kernsatz hat.
Also das ist, das kommt auch oft erst Jahre später. Wenn die Software,
wenn die Programmiersprache schon jahrelang im Betrieb ist und eingesetzt wird,
dann kommen die Theoretiker und versuchen das dann sehr formal zu fassen.
Okay, das ist eigentlich heutzutage auf der Ebene kaum noch angesagt.
Es ist sicherlich gut, wenn man das auch in einer vernünftigen Programmierausbildung mit berücksichtigt.
Wir machen es hier relativ gering vom Schwerpunkt her.
Ich vermute, dass ihr in FG1 oder 2
dann auch nochmal auf diese Themen zu sprechen kommt.
Das heißt also, diese Programmbeweise werden auch nur sehr,
sehr in engen Bahnen, in sehr klar umgrenzten Kontexten eingesetzt.
Aber was auch eine wichtige Rolle gespielt hat,
für die Entstehung der Objektorientierung waren die sogenannten abstrakten Datentypen.
Und da will ich jetzt mal so ein paar Worte zu sagen,
wie die so und was so die Kernideen sind.
Also, das ist ein weiterer Ansatz, zuverlässige,
korrekte Software zu entwickeln. Ich habe eben gesagt,
so die Hochzeit der imperativen Programmiersprachen, 60er, 70er Jahre,
immer diese Idee, ein Programm besteht aus Daten und Funktionen oder Daten und Prozeduren.
Und da war schon irgendwie in der Formulierung drin,
naja, die Daten sind getrennt von den Prozeduren.
Ich habe also irgendwie ein großes Programm mit irgendwie mehreren Prozeduren
und die arbeiten auf den Daten.
Und da muss man irgendwie Datenstrukturen sich angucken und man muss
sich irgendwie Algorithmen angucken
Zduren, die irgendwie auf diesen Datenstrukturen arbeiten. Das ist eine sehr,
sehr traditionelle und ziemlich tief verwurzelte Trennung in der Informatik.
Die prägt die gesamte Informatik bis heute,
deswegen gibt es immer noch Vorlesungen wie Algorithmen und Datenstrukturen,
weil natürlich die Algorithmik sehr, sehr mächtig geworden ist,
sehr, sehr viel Inhalt produziert hat.
Aber im Sinne der Objektorientierung ist das nicht,
das haben wir ja hoffentlich hier so ganz gut vermittelt bisher,
weil wir natürlich eigentlich gerne die Datenkapseln in den Objekten
wenn man jetzt die Imperative klassische Trainer noch hat,
dann hat man irgendwann aber auch natürlich Typkonzepte eingeführt.
Aber auch da war es so, dass diese Definition der Typen
meistens getrennt war von den, auf den Typen arbeitenden Operationen.
Und mit diesen abstrakten Datentypen wurde erstmals gesagt, naja,
wir wollen eigentlich die Daten, auf denen wir arbeiten,
kapseln mit den Operationen.
Das heißt, man möchte nur wohl definiert über diese Operationen
auf die Daten zugreifen und kann damit Korrektheit leichter garantieren.
Deswegen gibt es sowas wie private Exemplarvariablen,
weil man dann garantieren kann,
dass von außen niemand diese privaten Variablen verändern kann
Erleichtert die Betrachtung der formalen Korrektheit. Die ersten arbeiten zu
so abstrakten Datentypen, die stammen so aus Anfang der 70er Jahre.
Also es gibt einen Paper von Barbara Liskov von 1974,
in dem sie diese Idee schon mal darstellt, noch wenig formalisiert.
Da geht es erstmal nur darum, naja,
in Programmiersprachen möchte man sowas machen.
Man möchte eigentlich gerne so Daten und Operationen auf den Daten
kapseln irgendwie zusammenbinden und zu einer Einheit werden lassen.
Und wie gesagt, das war anfangs noch sehr stark über
Programmiersprachen,
Konzepte motiviert
Und wurde dann im Laufe der Zeit stärker formalisiert.
Da haben sozusagen die formal ausgerichteten Informatiker sich mit großer Energie draufgestürzt,
weil das einfach ein sehr, sehr dankbares Thema ist.
Die Grundidee eines dynamischen oder abstrakten Datentyps ist,
nur über die Operationen zu definieren,
die anwendbar sind und die Bedingungen ihrer Anwendung zu beschreiben.
Also nicht nur festzulegen, es gibt die und die Operation,
das kennen wir von den Interfaces in Java.
Ich lege die Operationen fest,
sondern ich lege zusätzlich noch fest, welche Bedingungen gelten müssen,
damit ich
sie anwenden kann
Dabei sollen diese Operationen möglichst abstrakt sein. Das heißt,
nicht durch eine Implementation definiert sein. Das heißt,
ich kann natürlich einfach eine Klasse hinlegen und sagen,
hier, diese Klasse definiert Operationen,
die öffentlichen Methoden definieren ja die Operationen und guckt auch hin,
die Semantik von Java ist bekannt,
du siehst den Code, dann weißt du doch,
was da passiert.
Semantik ist doch klar. Das ist aber nun gerade nicht
die Idee von den abstrakten Datentypen,
sondern deswegen heißen die ja abstrakt,
die abstrahieren von konkreten Implementationen.
Das ist also eigentlich eher
eine Spezifikation
Und die insbesondere, das ist spannend, unabhängig von einer konkreten Programmiersprache ist.
Wenn ich in Java ein Interface benutze, um etwas zu spezifizieren,
ist es eben halt oft Java-Syntax festgelegt und auch auf die Sprachmöglichkeiten,
die Java mir bietet, zum Beispiel zur Parameterübergabe.
In anderen Sprachen gibt es wieder andere Interface-Möglichkeiten, in C-Sharp zum Beispiel.
Und diese abstrakten Datentypen sollen davon komplett unabhängig sein.
Also es ist einfach eine Idee von,
ich möchte Schnittstellen von Typen beschreiben können, ohne festzulegen,
wie sie implementiert sind.
Und das ist für uns nichts Neues.
Im Grunde genommen ist das das, was wir
die ganze Zeit machen
Nur jetzt seht ihr sozusagen im Nachhinein, wo das ganze Zeug auch teilweise herkommt.
Also es gibt zwei ganz wichtige Wurzeln für die Objektorientierung.
Einmal abstrakte Datentypen Anfang der 70er und dann die Idee von Capsalon
und im Geheimnisprinzip von Panas, auch Anfang der 70er.
Also da sind tatsächlich schon mindestens die Wurzeln der Objektorientierung klar zu sehen.
Also was wir dann eben für uns üblicherweise schon kennen,
Zugriff auf die Dienstleistung einer Klasse nur über die Schnittstelle,
Capsalon der Exemplarvariablen, verbergen und potenzielles Austauschen der Implementation von Operationen.
Gehört ganz zentral dazu
Ja, strakter Datentyp ist ja, wie gesagt,
nichts, was wir uns jetzt hier ausgedacht haben.
Unter einem Datentyp versteht man die Zusammenfassung von Wertebereichen und Operationen
zu einer Einheit. Das ist so ein bisschen was,
was wir auch schon zum Typbegriff gesagt haben.
Wertemenge plus Operationen drauf. Da liegt der Schwerpunkt auf
den Eigenschaften. Dann spricht man von einem abstrakten Datentyp,
also auf diesen Eigenschaften, die diese Elemente dieses Typs dann haben.
Und wenn man das jetzt formaler fasst,
und das ist eben das, was ich sagte,
so in den 70er Jahren,
wo du dann diese Idee von abstrakten Datentypen aufgegriffen und dann
immer weiter formalisiert,
hin zu sogenannten allgebranischen Spezifikationen
Mit denen man dann abstrakte Datentypen beschreiben kann. Und die besteht
eine solche Definition aus der Benennung von Typen, auf denen gearbeitet wird.
Funktionen, wir würden sagen Operationen, dann Axiummel,
das ist was, was wir bisher nicht kennen.
Und Vorbedingungen, die haben wir schon teilweise gesehen für unsere Vertragsmodellanwendung.
Hier ist einfach mal so ein ganz einfaches Beispiel.
Also hier eine formale Beschreibung eines abstrakten Datentyps, der Typ heißt Nut.
Die natürlichen Zahlen. Dann gibt es die Functions und
da werden jetzt hier Signaturen festgelegt.
Signaturbegriff kennt ihr ja
Also es sind mehr oder wieder die Operationsköpfe.
Und da gibt es so etwas wie hier,
da ist so immer so eine Abbildung,
also etwas so ein Pfeil, da wird irgendwas erzeugt.
Und hier, wenn da zwischen dem Doppelpunkt und dem Fall nichts steht,
dann ist das sozusagen so, das ist die Benennung der Operation,
also der Funktion.
Und es kommt daraus ein Exemplar, ein Element dieses Typs.
Also ein Element aus der Wertemenge von Nut.
Und man könnte sagen, in Objektentitel,
Lingo ist das hier
ein Konstruktor
Er erzeugt aus Nichts eine natürliche Zahl, ein Nut-Exemplar.
So, und dann gibt es sowas wie Suk,
also Successor, kriegt eine natürliche Zahl als Parameter.
Das ist sozusagen jetzt hier, was reinkommt und dann rechts,
was rauskommt. Also ist eine Funktion,
die von dem Wetterbereich der natürlichen Zahlen,
der Nut-Elemente auf den Wetterbereich der Nut-Elemente abbildet.
Und Ed ist auch etwas,
das hat aber dann hier so Kreuzprodukt als Parameter,
also zwei Nut-Elemente und erzeugt ein Nut-Element
Also zwei natürliche Zahlen werden addiert und es kommt eine neue
natürliche Zahl raus. Das ist mehr oder weniger das,
was wir in der Objektorientierung auch können,
dass da haben wir keine Probleme mit.
Hier kommt jetzt der Teil, der spannend ist.
Jetzt kommen nämlich hier die gesunden Aktionen.
Deswegen akzeomatischer Semantik für For Annie, I.J. aus Not,
also für beliebige Variablen vom Typ Nat.
Oder für Variablen, I. und J. gelte.
Wenn ich anwende die Operation at i0,
dann ist das Ergebnis null
Und wenn ich at E auf den Successor von J anwende,
dann ist das das Gleiche, als wenn ich
den Successor auf die Addition von E und J anwende.
So, das heißt, ich formuliere sehr abstrakt, wie ich die Operation anwende.
Deswegen haben wir auch immer sehr großen Wert darauf gelegt,
dass ihr mit Schnittstellen umgehen könnt, hier in SE1,
in der SE2, dass ihr die Idee von Schnittstellen habt,
was ihr auch aus Sicht eines Klienten denkt und nicht immer gleich in Implementationen,
sondern euch überlegt, was ist denn die Dienstleistung,
die nach außen angeboten wird?
Das hier ist komplett Urdienstleistung und überhaupt nichts darüber,
wie es implementiert ist. Hier werden einfach nur Aussagen darüber
getroffen, welche Bedingungen
dann gelten sollen
Also wenn diese Aktion mal alle zutreffen. Das heißt,
die Aktionen sagen tatsächlich etwas über die Semantik. Und letztendlich,
was da passiert, ist, dass da Algilbrind definiert werden,
da seid ihr wahrscheinlich deutlich fitter als ich mit meinen veralteten,
verrosteten Mathekenntnissen,
aber ist mathematisch ist eine allgemeine Wertemenge und die Operation auf diesen Werten.
Und da gibt es eben mehrsortige Algeberinnen,
werden wir auf die nächsten Folien noch sehen.
Die verwenden dann mehrere Sorten als Wertebereich. Das ist hier
eine Sorte. Okay, das Beispiel ist ein bisschen reichhaltiger,
deswegen auch ein bisschen, sagen wir, interessanter.
Hier haben wir jetzt einmal so etwas wie den Typ Stack.
Und dann hat man aber auch wieder sowas hier in eckigen Klammern Tee.
Das ist ein generischer Typ. Ein generischer, allgebranischer Datentyp.
Also ein Stack von Elementen, eines bestimmten Typs Tee.
Und dann gibt es hier so eine Anweisung im Ports Bulien.
Also man braucht anscheinend Bulien, um hier Dinge aussagen zu können.
Und zum Beispiel hat man hier, die Operation ist Empty.
Ja, ich kann bei einem Stack,
also die Operation des Empty hat als Parameter ein Stack von
Element Typ Tee
Und liefert dann ein Bullien. Also nach dem Motto,
ja, ist entweder leer oder nicht leer.
Dann gibt es hier die Funktion und das ist wieder das,
was ihr kennt. Also erstmal MT ist mal wieder ein Konstruktor,
in Anführungsstrichen. Ich kann also das Ding jederzeit aufrufen,
ich muss nichts reinstecken, kriege ein Stack von Tee raus.
Dann kann ich definieren, dass es eine Operation Push gibt.
Und da ist es so,
ich muss einen Stack reinstecken und irgendwie ein Element,
das auf den Stack gepusht werden soll
Und jetzt kommt etwas, das ist ein bisschen anders als wir es kennen,
weil es als Ergebnis kommt hier raus, Stack von Tee.
Das heißt, das ist eher eine funktionale Anwendung von
diesem Konzept von etwas auf einen Stack drauf pushen.
Es ist also, nee, bekomme einen Stack und liegt noch
ein Element und dann liefere ich als Ergebnis einen Stack,
auf dem dieses Element drauf ist.
Da wird nichts darüber ausgesagt,
irgendwie, ob der Stack sich verändert in seinem Zustand,
ob das derselbe Stack ist oder ob das ein anderes Deck ist.
Da ist eher die Aussage, naja,
es ist ein neuer Stack mit diesem Element
da drauf
Da steht dich aber erstmal noch nicht,
das ist erstmal nur die Signatur, Pop genauso.
Ich gebe also einen Stack rein und bekomme dann einen anderen Stack da rausgeliefert.
Top liefert mir von einem Stack das oberste Element,
das steht da nicht, aber ich wüsste,
ich sage das jetzt schon mal,
und es empty liefert von einem Stack, ob er leer ist oder nicht.
Und das ist jetzt hier wieder beschrieben über diese Aktionme.
Das heißt, auch hier wieder völlig unabhängig davon,
ob ich das mir jetzt, also ihr wisst,
dass man, wie man so ein Stack implementieren kann.
Man kann das als verkettete Liste implementieren als Array Stack,
was weiß ich was. Irgendwelche Möglichkeiten gibt es da.
Darum geht es überhaupt nicht, sondern nur ausschließlich darum,
wie benutze ich denn sonst Deck? Was leistet er mir?
Und insbesondere wird hier jetzt auch dieses Liefo-Verhalten mitdefiniert. Ja,
also für jeden Stack von TS und für jedes Element vom Typ TX
gelten diese ganzen Aktionen und die müssen immer gelten.
Also zum Beispiel, wenn ich auf ein Stack,
ein Element drauf pushen und hinterher dann darauf Top-Aufrufe,
dann kommt das Ex wieder raus.
Das ist hier implizit die Liefo-Semantik. Das,
was ich zuletzt draufgepusht habe, kommt mit Pop
wieder runter
Oder wenn ich etwas drauf pushe und dann Pop aufrufe,
dann kriege ich den um ein Element verkürzten Stack.
Also das S ist da ja, dieses X ist auf
den Stack draufgepusht. Und wenn ich dann Pop aufrufe,
kriege ich wieder genau den verkürzten Stack. Das heißt,
man hat hier sehr, sehr klar und präzise,
akzematisch beschrieben, wie die Semantik ist.
Plötzlich steht da mehr als... Hier ist eine Signatur für
ein Stack, ein paar Interfaces oder so,
sondern tatsächlich, ich habe jetzt Semantik definiert.
Ist Empty von Empty zum Beispiel Liefer True und ist Empty
von irgendeinem Stack, auf den mindestens ein Element draufgepusht
ist,
liefert False
Ja, also da ist dann einfach,
ist Empty definiert an dieser Stelle. Das ist ganz schön,
was man auch sieht ist hier,
das habe ich glaube ich schon in SE1 irgendwann mal angedeutet,
hier sind manche Dinge, manche Pfeile sind so durchgestrichen,
das soll andeuten, dass sie partiell sind.
Das heißt, ich kann nicht auf jedem Stack Pop aufrufen.
Zum Beispiel darf ich auf einen leeren Stack nicht Pop aufrufen,
weil da kann man ja nichts mehr runternehmen.
Auch top auf einem leeren Stack darf ich nicht aufrufen,
weil top vom Lernsteck
liefert mir nichts
Das heißt, diese Operation sind partiell und partiell in dem Sinne,
dass es dann Vorbedingungen gibt, die dann sagen,
also, Pop auf einem beliebigen Stack erfordert,
dass es nicht leer ist.
Und auch top erfordert das Gleiche. Das heißt,
es wird hier noch ergänzt, diese Idee von den Aktionen,
die möglichst exakt die Semantik beschreiben sollen, um diese Vorbedingungen,
um so partielle Funktionen auch definieren zu können.
Ja, das, wie gesagt, ist stark formalisiert.
Da gibt es
eine Frage
Lasst, wenn auch euer Kopf ins Bett. Könnt ihr mal leise sein,
sorry, ich habe es gerade nicht verstanden, sorry,
nochmal einmal ein bisschen lauter, bitte. Kann man mal dann ausgehen,
dass wenn Funktionen bei dem Falschen sind,
dann war es seltsam, die ich jetzt heute vorsprochen habe,
dass sie da entweder bei der Aktion, wo aber im Flüpermöschensland spricht Gott,
da werden Wasser angepasst, oder wie Podius.
Das sollte eigentlich so sein, ja, so würde ich erwarten,
ganz genau. Die Frage ist, die er gestellt hat,
ich wiederhole sie kurz für die Aufnahme,
ob man davon ausgehen kann,
dass wenn da Funktionen sind und die partiell sind,
dass dann das entsprechend auch durch die Pre-Conditions abgedeckt ist,
dass man dann da erklärt,
warum sie partiell sind
Ich könnte mir vorstellen, dass die Frage auch daher rührt,
dass im Skript das, was da drinsteht, teilweise nicht vollständig ist.
Ich habe auch gleich so einige Korrekturen hier,
die ich dann andeute. Gut, aber erst mal,
ich glaube, ist die Grundidee klar geworden.
Hier ist mal die Liste, einfach die Idee einer Liste als abstrakter Datentyp.
Also hier wieder importiert Bulien eine Liste von Element Hyptee.
Da gibt es auch wieder eine leere Liste.
Dann kann ich First aufrufen und da sieht man schon wieder
, da ist es auch partiell.
Das ist im Skript leider nicht richtig. Denn hier unten
steht ja als Vorbedingung First von einer Liste erfordert,
dass die nicht leer ist
Also muss das hier entsprechend als partiell gekennzeichnet sein.
Rest genauso. Rest liefert mir von einer Liste.
Nicht das erste Element, sondern den Rest der Liste.
Das ist eine sehr funktionale Sicht auf Listen. In der
funktionalen Programmierung ist das ganz selbstverständlich, dass man Listen so auffasst.
Das ist eine andere Auffassung von Listen,
als wir sie haben, jetzt aus SE1 und SE2.
Wir haben euch Listen ja definiert als eine Liste,
so eine Reihung von Objekten.
Duplikate sind zugelassen und ich kann die Reihenfolge explizit manipulieren und
ich kann auch wahlfrei irgendwie auf bestimmte Positionen mit einem Index zugreifen.
Dieser Zugriff über einen Index, der ist so Aha, der ist nur umstritten.
Also hier in dieser Definition zum Beispiel ist ja nicht drin.
Es ist aber nicht, das ist ja jetzt nicht die Definition einer Liste.
Auch da gibt es natürlich wieder ganz verschiedene Arten, wie man das machen kann. Aber man sieht hier,
also das ist der Kopfanalyst, das ist der Rest an der Liste,
er pennt, ich kann dann etwas an eine Liste,
ein Element, eine Liste anfügen,
bekommen wir dann eine um ein Element verlängerte Liste, concut,
ich kann also zwei Listen zusammenfügen, konkratinieren und ich kann wieder fragen, ob eine Liste leer ist.
Und dann sind hier wieder so die üblichen Verdächtigen und Verdächtigen,
was so Aktionen mal angeht. Die will ich jetzt nicht alle im Einzelnen durchgehen,
das könnt ihr euch in Ruhe nochmal so ein bisschen angucken,
ich will nur andeuten, hier, wie gesagt,
partiell auch auf dieser Folie, waren die Operationen DQ und Front nicht als partiell gekennzeichnet im Skript.
Das müssen sie sein, weil die ja hier wieder mit Preconditions versehen sind.
Hier die Schlange als ADT und dann sieht man eben halt,
dass eine Schlange anders definiert ist als ein Stack.
Da ist einfach die Semantik dann auch eine andere.
Hier mal ein Beispiel, wie eine Menge definiert werden kann.
Und das ist jetzt mal so ein kleines Suchbild.
Und mal schauen, ob ihr auch alle aufgepasst habt.
Hier sind nämlich Fehler drauf. Also immerhin ist hier die Lead wieder als Partie gekennzeichnet,
weil die Lead hat ja hier die Vorbedingung,
dass das nicht leer sein darf,
wenn ich ein Element aus einer Menge herauslöschen will.
Ich kann also die aus der Menge S, das Element X, auslöschen.
Require not is mTS. Also ist das eigentlich partiell, dieses D-Lead?
Wem fällt hier was auf, was vielleicht nicht funktioniert?
Guckst du euch erstmal in Ruhe an? Also ihr seht hier die Operation,
Insel, die Lead, Union Interception, Difference,
also so Mengentheoretische Sachen, dann die Frage,
ob man ein Element enthalten ist, juhu kennen wir ja,
haben wir ein SE1 ausführlich beleuchtet,
dann ist Empty,
ob eben halt eine Menge leer ist
Und dann gibt es eben halt hier diese Aktione. Also zum Beispiel,
er ist Element, ob ein Element enthalten ist,
bei einer leeren Menge ist natürlich kein Element, X enthalten.
Deswegen liefert das Falls. Wenn ich die Lead auf einer Menge habe,
wo ich ein Element X löffel, dann ist das irgendwie
semantisch äquivalent zu der Differenz von S und Insert MTX.
Also ich füge in eine leere Menge X ein,
habe also eine Menge mit dem X,
dann ziehe ich von S diese Menge ab,
Mengen theoretisch und dann weiß ich,
dass das eben halt das Gleiche ist wie die
Lead S von
So, wo seht ihr hier einen Fehler? Hat jemand
eine Idee, wenn ihr euch die Aktionen noch so anguckt?
Ja, das ist vielleicht bei einem einfließen, dann merke ich das mal.
Du meinst, also beim Einfügen sollte man prüfen,
ob das Element nicht schon vorhanden ist?
Ja, es ist eigentlich relativ unwichtig.
Also nehmen wir mal an, es ist nicht drin,
hinterher ist es auf jeden Fall drin.
Nehmen wir mal an, es ist drin,
dann ist es hinterher auch auf jeden Fall drin.
Also das macht keinen Unterschied sozusagen. Nö,
das ist schon in Ordnung her. Hat jemand sonst noch
irgendwo vielleicht einen Hinweis, eine Idee,
wo hier ein Fehler sein könnte
Ganz banal. Man kann es tatsächlich parsen.
Hier, Insert. Hier unten ist dieses Axtum,
das sagt Empty, also eine leere Menge.
Wenn ich die frage, ob sie leer ist,
dann liefert die True. Und dann steht hier Insert,
XMT, ist gleich False. Das kann gar nicht sein,
weil Insert, wenn man mal genauer hinguckt,
Insert liefert ein Set von Tee und kein Bulien.
Also offensichtlich ist hier irgendwas falsch kopiert worden. Da müsste
wahrscheinlich stehen, ist Empty von Insert XMT des False.
Also wenn ich in eine leere Menge etwas einfüge,
gilt garantiert, dass sie nicht leer ist.
Das fehlt hier einfach
Hier könnte man vielleicht auch noch mal so ein bisschen genauer hingucken.
Die Lead, die Lead ass aus X Require,
not is empty. Da könnte man jetzt auch sagen,
naja, da muss es dann nicht auch drin sein,
das Element. Naja, da kennt man auch etwas.
Bitte, ich habe es nicht verstanden, ein bisschen lauter.
Dass man liebt, wenn man das aufbringt.
Genau, also das erfordert hier nicht, dass das Element
wirklich enthalten sein muss
Genau, auch von diesen Aktionen hier. So,
ihr seht, man kann tatsächlich dann über die Semantik reden.
Es ist formal erfassbar. Man kriegt also mehr Informationen.
Man kriegt das besser zu greifen. Das ist sehr schön
. Hier ist nochmal ein Binärbaum auch im Skript.
Auch da fehlten wieder hier die partiellen Hinweise,
dass also Value, Left und Ride natürlich alle partiell sind,
weil sie alle Vorbedingungen haben.
Okay. Wir haben jetzt gesehen sozusagen altgebranische Spezifikationen.
Es ist eine feine Sache. Müssen nur aufpassen.
Die sind anders als, was wir in der Objektorientierung machen
Also, diese allgebranche Spezifikationen sind funktional bzw. applikativ ausgerichtet.
Das heißt, die kann man nicht so in so ein iperatives OO-Programm übertragen.
Auf jeden Fall bleibt aber natürlich die Grundidee, ich kann
einen Datentyp durch sein Verhalten definieren und nicht über seine Implementation.
Das ist eine super Sache, das möchte man gerne haben.
Also eine ADT-Spitzfähigkeit, sondern es ist nicht funktional und nicht
an Objekten und Operationen orientiert. Also genau dieser Unterschied hier
. Wenn ich Pusch in einer allgebrachten Spezifikation definiere,
dann sieht die Signatur so aus,
Push ist definiert als,
gib mir ein Stack von Tee und ein Element vom Typ Tee
und dann kommt ein neuer Stack Tee raus
Während wir in der Objektorientierung,
das ja zustandsbasiert machen würden und sagen würden, naja,
ich rufe auf dem Stack, der einen Zustand hat,
einen Push auf mit einem Element vom Typ T.
Und dann hat sich dieser Stack auch verändert. Das ist
eine ganz andere Auffassung. Und es ist tatsächlich so,
dass diese Agribraischen Spezifikationen eine Zeit lang als ziemlich spannend gehandelt wurden,
sagt dem Wort, oh,
die können für die Objektorientierung wirklich eine wichtige Rolle spielen.
Wenn man aber genau hinguckt, ist das nicht so richtig passiert.
Weil die Objektorientierung eben halt sehr viel mit zustandsbasierter Programmierung
zu tun hat
Und das ja auch bewusst will, also ja eben halt nicht versucht,
Zustände auszublenden. Es gibt andere Paradigmen in der Programmierung,
die auf Zustände verzichten und dadurch Vorteile haben,
aber für realitätsnahe Modellierung ist es oft so,
dass man Zustände mit einbeziehen muss.
Und was wir hier sehen, ist, dass diese Möglichkeit,
diese Zustände mit einzubeziehen, Schwierigkeiten macht.
Also diese Tatsache, dass da etwas zustandsbehaftet ist,
lässt sich formal
extrem schwer modellieren
Aber de facto wird es eben halt in OO-Programmiersprachen gemacht.
Was aber ganz spannend ist, das,
was wir hier so sehen, diese funktionale Sichtweise auf diese abstrakten Datentypen,
die spiegelt sich genau wieder in unseren Fachwerten,
in unseren Werttypen, die wir euch nähergebracht haben.
Also wir haben ja gesagt, Werttypen sind so
eine Spezialausprägung der Objekttypen in Objekten und den Promiersprachen.
Diese Werttypen sind unveränderlich, genau wie diese abstrakten Datentypen.
Da sind diese Elemente auch unveränderlich. Und ich habe tatsächlich dann so Operationen,
die dann immer wieder neue Elemente von diesem Typ liefern.
Ich kann das hier mal zeigen weil ich habe ja hier
Blue Jay am Start. Ich habe Blue Jay am Start.
Ich habe Blue Jay am Start. Ich habe Blue Jay
am Start. Na gut, muss ich halt klicken.
Geldbetrag. Geldbetrag sollt ihr gerade in den Übungen modellieren.
Sollt ihr auch programmieren. Geldbetrag ist etwas,
was wir ganz klar als einen Werttyp ansehen.
Und hier ist es jetzt tatsächlich wieder so,
ich habe hier, wenn ich mir jetzt die Schnittstelle ansehe,
das ist eine Implementation, die ich gebaut habe,
auf Basis einer Musterlösung bei uns aus dem Übungsbetrieb.
Ich habe hier
zwei Operationen.
Über ein Int und Get über einen String. Ich kann
also mir zum Beispiel jetzt hier über einen Int 100,
kann ich mir hier einen Geldbetrag geben lassen,
lasse ich mir hier auf die Objektleiste legen.
So, das ist ein sogenannter Selektor, den ich hier gerade aufgerufen habe.
Ich habe jetzt also genau ein solches Element. Und dieses Ding kann ich jetzt nicht verändern,
wenn ich jetzt, ich kann mir jetzt hier irgendwie,
kann fragen, ob dieser Geldbetrag größer gleich einem anderen Geldbetrag ist,
aber die Operationen, die man auf einem Geldbetrag machen kann,
zum Beispiel addieren,
die habe ich hier immer noch als Klassenoperation modelliert.
Also wenn ich jetzt hier sage, addiere,
dann kann hier zwei Getbeträge haben. Ich kann mir so
erst mal jetzt von diesem hier das to string aufrufen,
dann sehe ich hier, das ist also ein Euro.
Ich mache mir noch mal einen zweiten Geldbetrag.
Irgendwie in 200 Cent, also zwei Euro, okay.
Und dann habe ich hier beide zwei Geldbeträge. Und die
kann ich jetzt addieren. Ich kann also jetzt sagen,
hier addiere und dann gebe ich den einen rein und dann den anderen,
ups, nee, erst den einen.
Und dann hier den zweiten. Und als Parameter übergebe ich
zwei Exemplare dieses Typs und rauskommt ein neues Exemplar,
möglicherweise ein schon bestehendes Exemplar,
aber es ist eben halt,
es kommt etwas von dem gleichen Typ
da raus
Ich verändere nicht die Dinger selber,
sondern kann jetzt hier mir das Ding einfach auf die Objektleiste
legen und habe dann hoffentlich einen Geldbetrag, der irgendwie drei Euro ist.
Und das heißt, das sind eigentlich hier genauso im Sinne
von abstrakten Datentypen, sind diese Operationen an Werttypen.
Also entweder diese Klassenoperationen, die so auch symmetrisch sein können
oder eben halt diese nicht symmetrischen Operationen wie größer gleich.
Also ich kann zum Beispiel fragen,
ist dieser Betrag größer gleich diesem und dann kommt hoffentlich Falls raus,
weil ein Euro üblicherweise nicht größer ist als 2 Euro,
aber der ist ziemlich sicher größer gleich in diesem Betrag.
So, ne? So, und dann kommt da auch
ein Schuh raus. Das ist wieder eine Frage der Modellierung.
Habe ich die Operation an den Exemplaren modelliert, sind das Exemplar-Operationen?
Oder habe ich die eben halt an der Klasse modelliert?
Die Möglichkeiten haben wir für so arithmetische Sachen ist es eigentlich
immer schöner, das symmetrisch zu machen.
Also hier addiere tatsächlich so zu bauen, dass die beiden Beträge,
die addiert werden, als zwei Parameter übergeben werden.
Das ist deshalb besser, weil ich dann das Problem nicht habe,
dass der eine von beiden Nall sein kann.
Ich kann jetzt, wenn ich das so aufrufe,
kann ich damit umgehen, dass einer von beiden Nall ist
Einfach ja konkret in der Java-Programmierung.
Während wenn ich jetzt das addiere als eine Exemplaroperation baue,
also Geldbetrag, addiere dich zu einem anderen Geldbetrag.
Auch das könnte ich ja funktional bauen. Das heißt also
, als Ergebnis des Aufrufs kommt ein Geldbetrag raus.
Aber dann kann es eben sein, dass der eine Nall
ist und dann ist gerade der Aufruf geht dann schief.
Null Pointer Acception. Also deswegen ist das eigentlich angenehmer,
dass solche symmetrischen Operationen auch über Klassenoperationen zu modellieren.
Und einfach nur, dass uns das klar ist, juhu,
eine gebranche Datentypen und unsere Werttypen, die sind ziemlich dicht beieinander.
Die liegen, die sind deutlich verwandter als die Objekttypen,
die wir in der Objektorientierung haben.
Die ein Gebranchendatentypen passen nicht gut zu zustandsbehafteten Objekten,
die wir aber ja wichtig finden in der Softwareentwicklung.
Okay, was wir aber auch gesehen haben,
ist Vor- und Nachbedingung, also insbesondere Vorbedingungen,
also Vor- und Nachbedingungen aus dem Horrkalkül oder Vorbedingungen aus den altgebranchen Datentypen.
Dieses Konzept von logischen Prädikaten,
die man dann auch in den Schnittstellen seiner Klassen anwenden kann.
Das haben wir gelernt, das ist durchaus nützlich und
damit kann man schon relativ viel Semantik ausdrücken.
Also wo immer das geht, sollte man es probieren.
Also auch da haben wir sehr viel von den Theoretikern gelernt,
in die Praxis gezogen. Also hohe praktische Relevanz. Okay,
das war jetzt so die erste Runde zum Thema formale Spezifikation von Semantik von Programmiersprachen.
Gibt es da noch Fragen dazu? Das sieht nicht so
aus, dann machen wir fünf Minuten Pause
Bevor ich weitermache, eine ganz kurze Korrektur,
ich muss mal eben gucken, 22, ja,
21, nee, 20, der Stack als ADT,
Kommiliton hat mich gerade darauf hingewiesen,
dass hier in dem grauen Kastenstand die Funktionen Push und Pop
sind partiell.
Das ist natürlich Quatsch
Pop und Top sind partiell. Pop und top.
Und nicht Push und Pop. Okay. Habe ich
jetzt hier korrigiert. Im Skript steht es noch falsch.
Sehr fehlerhafte Folien diesmal. So, ja, zweiter Teil.
Yuhu, jetzt mal was ganz anderes. And now to
something completely different. Metasprachliche Ansätze in der Programmierung.
Also, was ist das? Naja, das ist auch
etwas, was es in Java gibt.
Deswegen hier auch der Hinweis auf die Java-Sprachbeschreibung
Es ist so, wenn wir mit einer Programmiersprache intensiver uns beschäftigen,
dann stellen wir relativ schnell fest,
dass wir auch über die Elemente in der Programmiersprache nachdenken.
Also sowas wie Methoden, sowas wie Klassen werden dann auch
zu Elementen der Betrachtung. Also es ist auch häufig so,
dass wir zur Laufzeit erst Elemente von diesen Typen,
die wir so üblicherweise programmiert haben, erzeugen können. Das ist normal,
dass wir in unserem Programm dann dynamische Objekte erzeugen,
was nicht so üblicherweise typisch ist, dass man noch gar nicht weiß,
von welcher Klasse etwas sein muss,
dass man zum Beispiel Beispiel dynamisch zur Laufzeit,
den Quelltext einer Klasse, der vielleicht vorher von einem Programm erzeugt wurde,
lädt und dann plötzlich an den Klasse im System hat.
Denkbar ist sowas. Manchmal braucht man das,
gerade wenn man so Interpreter schreiben will, auch in Programmierumgebung.
Klar, wenn ihr ihr Clips euch anguckt,
die müssen auch ziemlich viel wissen über die Elemente der Programmiersprache.
Auch wenn wir uns dann mit fortgeschrittenen Konzepten der Programmierung beschäftigen,
so mit Einbindung von Datenbanken,
wo man dann irgendwie auch aus Datenbanken irgendwie Elemente bekommt,
textueller Art und dann die umwandeln muss in bestimmte Objekte
von bestimmten Klassen
Dann braucht man immer so so, ja, meta-sprachliche Eigenschaften in Programmiersprachen. Also immer,
wenn wir Programmteile schreiben, die das Programm selbst analysieren,
also auch zum Beispiel was wie ein Compiler, dann brauchen wir sowas wie meta-sprachliche Konzepte.
Und da gibt es eben halt in den meisten Sprachen ähnliche Ansätze,
auch in Java und die werden wir uns ein bisschen für Java ansehen.
Und der Unterschied ist tatsächlich,
wenn wir bisher so gesagt haben, naja,
wir haben einen Anbindungsbereich, für den modellieren wir unsere Software,
wir gucken in den Bankenbereich, wir sehen Kontos oder konnten,
wir sehen Kontostände, Kontonummern, Also Geldbeträge,
dann modellieren wir da den Anwendungsbereich über die fachlichen Begriffe und ziehen die in unsere Anwendungssoftware rein.
Aber man kann eben halt auch einfach tatsächlich ein OO-Programm selbst als Gegenstand der Modellierung ansehen.
Also wenn ich einen Compiler baue, dann muss ich mich mit,
dann ist meine Fachlichkeit, ist plötzlich Programmieren oder Programme.
Und dann wollen wir die Konstrukte des Programmes tatsächlich auch darstellen können.
Und üblicherweise, in der Objektanführung wollen wir die als Objekte repräsentieren.
Das heißt, wir wollen zu den Objekten,
die wir so klassisch kennen, auch Objekte haben,
die diese Objekte beschreiben auf der Meta-Ebene, sogenannte Meta-Objekte.
Und wenn man so etwas, so ein Protokoll hat,
also in einer Sprache, Operation angeboten bekommt,
damit umzugehen, dann nimmt man das ein Meta-Object-Protokoll-Mob.
Das werdet ihr in den meisten OO-Sprachen auch einen funktionalen Sprachen teilweise zu sehen bekommen.
Das ist also durchaus spannend, was man da so machen kann.
Und wie gesagt, wir gucken uns das für Java an.
Hier nochmal so ein bisschen dieser Unterschied, ich habe einen Anwendungsbereich,
dann baue ich für einen bestimmten Zweck ein Modell des Anwendungsbereiches,
mache also eine Verkürzung, also betrachte nicht alles,
was im Einwendungsbereich ist, als relevant,
sondern nimm nur die relevanten Teile für eine bestimmte Problemlösung.
Dann habe ich mein Modell meines Anwendungsbereichs. Und dann mache ich
davon irgendwann dafür für dieses Problemlösungsmodell irgendwann ein ausführbares Programm.
Und das ist dann tatsächlich irgendwo in einer Virtual Machine.
Und dann gibt es aber auch das Programm selbst.
Da gibt es dann ein Modell des Programms, also Ideen,
aus welchen Elementen denn so ein Programm besteht.
Und dann hat man eben halt tatsächlich in dieser Laufzeitumgebung
auch sowas wie Metakonstrukte
Einfach nur mal, um das noch mal an natürlicher Sprache zu verdeutlichen.
Wir machen mit unserer Sprache, ich spreche jetzt hier Deutsch
zum Beispiel, machen wir Aussagen über unsere Umwelt.
Das Kind spielt mit dem Ball. Das ist ein Satz,
den wir vermutlich alle verstehen in seiner Semantik.
Wir können mit unserer Sprache auch Aussagen über unsere sprachlichen Äußerungen machen.
Also wir können uns auf der Meta-Ebene darüber äußern.
Wir können sagen, das Kind ist Subjekt des vorigen Satzes.
Also hier, das Kind ist Subjekt des Satzes.
Das heißt, ich mache eine Aussage über etwas anderes
Das ist eine metasprachliche Aussage. Und dann kann ich tatsächlich
ein Meta-Modell haben, nämlich Sätze sagen,
die etwas darüber sagen, wie denn meine Sätze aussehen.
Also Sätze bestehen oft aus Subjektprädikat, Objekt. Das ist ein Meta-Modell,
also ein Modell von Sprache, das da beschrieben ist.
Am Rande bemerkt es natürlich auch sowas immer sehr schön.
Man kann sehr schnell in Paradoxien laufen.
Also wir machen alles mit Sprache, sind aber verschiedene Ebenen,
kann es eben halt auch zu sowas kommen wie hier,
bitte beachten Sie diesen Satz nicht.
Das ist relativ schwierig, ihn nicht zu beachten
Wenn man ihn schon lesen muss, um ihn zu verstehen.
Gut, wenn wir das jetzt auf Programmiersprachen beziehen,
also konkret jetzt in einer Programmiersprache,
will ich jetzt nicht nur sagen, es gibt eine Klasse Konto,
sondern es gibt so etwas wie Klassen.
Und es gibt nicht nur die Operationen, Einzahlen und Auszahlen auf einem Konto,
das ist die Semantik, sondern es gibt allgemein Methoden auf Objekten.
Und genauso ist es eben halt tatsächlich dann auch hier in der Objektorientierung, Objekte.
Man kann also erstmal diese Meta-Objekte, diese Meta-Beschreibung einteilen,
in die, die das Programm statisch beschreiben.
Also ich habe Java-Programm mit fünf Klassen,
dann kann ich irgendwie für jede Klasse mir geben lassen,
welche Konstruktoren hast du, wie heißt die Klasse,
wie viele Methoden hat sie und so weiter.
Auch welche Parameter haben die Methoden? Und dann gibt es
aber auch sowas wie, ich möchte auch auf dieser Meta-Ebene
mit den Objekten etwas machen können zur Laufzeit.
Also ich möchte nicht nur wissen, diese Klasse hat fünf Methoden,
sondern ich möchte auch an einem Exemplar dieser Klasse eine dieser Methoden aufrufe.
Also tatsächlich hier Ausführung einer Methode oder die Belegung eines
Attributes,
eines Objektes auslesen
Hier steht immer noch Attribut. Ich habe jetzt versucht,
irgendwie überall Attribute durch Felder zu ersetzen. Denkt euch da
einfach auch Feld. Die Belegung der Felder eines Objektes.
Wir nennen die ja bei uns hier Felder, nicht Attribute.
Und deswegen gibt es eben halt so zwei Bezeichnungen.
Einmal die Reflexion, also in Java Reflection. Das sind
alle metasprachlichen Eigenschaften eines Programms. So wollen wir das jetzt
hier auch verstanden wissen. Also so verstehen wir Reflexion.
Und Introspektion ist Analyse der aktuellen Eigenschaften eines Objektes zur Laufzeit.
Also tatsächlich Feld auslesen oder eine Methode aufrufen,
also wirklich da so was machen.
Das nur als Begriffe, das ist jetzt einfach mal konkretisiert für Java.
Und ich denke mir, ich zeige einfach mal ein bisschen Code.
Das ist dann vielleicht gleich ein bisschen netter.
Man sieht hier, in Java können wir uns zu jedem Objekt
sein Klassenobjekt liefern lassen, das gleichzeitig seinen Typ repräsentiert.
Es gibt also diese Operation Get Cars.
Da muss man sehr aufpassen, dass es nicht das,
was wir bisher als Klassenobjekt kennengelernt haben.
Das Klassenobjekt ist ja das, was man in Blue Jay so sieht,
wo man dann die statischen Methoden dran aufrufen kann.
Das ist damit nicht gemeint. Sondern das ist tatsächlich
ein Objekt, also tatsächlich ein Java-Objekt,
dass die Klasse eines Objektes repräsentiert
Und ich habe das mal ein bisschen hier implementiert.
Das ist ein anderes Projekt. Das kennt ihr ausführlich,
also wieder das Übliche, unsere Titelliste,
kennt ihr aus SE1, eine Liste von Musiktiteln.
Dann habe ich hier fachliche Titel, die da drin sein können.
Dann habe ich zwei Implementationen, für die dieses Interface,
nämlich eine Array-Titelliste und eine Linktitelliste,
die Linktitelliste benutzen Doppel-Link-Knoten und ich habe eine Titelbibliothek,
die irgendwie zufällig Titel rausspuckt
Und ich habe zwei Testklassen, das kennt ihr.
So, und jetzt habe ich aber hier eine Klasse,
die nennt sich MOP-Demo, Mob-Demo, Metaophic-Protokoll.
Und die gucken wir uns jetzt mal an.
Die hat nämlich eine Operation hier und eigentlich hat sie
zwei Vergleiche aufrufe, die gucken wir uns später an.
Und dann Show-Info for Object. Ich kann da ein beliebiges
Objekt reinreichen. Und dann wird Informationen zu diesem Objekt ausgegeben.
Also ich kann jetzt zum Beispiel hier mir mal ein,
ich mache erstmal ein Exemplar von der Array-Titel-Liste, zack,
habe ich hier ein Exemplar.
Das ist ja Polymorph auch ein Object.
Jetzt kann ich also hier
diese Methode aufrufen
Show Info for Object. Zum Beispiel für diese RI-Titel-Liste. Und jetzt gucken wir erstmal,
was dieses Programm da jetzt tut, was ich da geschrieben habe.
Es spuckt hier irgendwie Informationen aus. Also,
klasse heißt Ray-Titel-Liste, implementiert die Interfaces-Titel-Liste, hat folgende Felder,
eine Titelliste, es scheint eine Ray zu sein,
das ist so eine komische Notation für Rays,
also private Felder, dann Anzahl der Titel,
Anfangskapazität und dann kommt ein Konstruktor und dann kommen Methoden.
Also sowas wie Füge ein, enthält die Gepitel,
entferne und so weiter.
Offensichtlich sind hier auch alle Methoden aufgeführt,
private und öffentliche
Ja. So, ich habe also irgendein Objekt reingereicht und
aus dem Objekt wurden dann diese Informationen irgendwie rausgesaugt.
Und wie das geht, gucken wir uns mal an.
Das heißt, das ist jetzt tatsächlich hier Show-Info 4,
beliebiges Objekt wird reingereicht. Dann wird aufgegerufen,
Show-Class-Info und dann rufe ich nämlich hier auf Orget Class,
also an dem Objekt rufe ich auf, gib mir dein Klassenobjekt.
So, und dieses Klassenobjekt wird jetzt hier übergeben und man sieht hier schon
diese Show-Class-Info bekommt als Parameter ein Glas mit so einem komischen Fragezeichen.
Das ist so ein bisschen blöde, weil auf den Folien steht hier nämlich noch,
Klaas, so, das wird so nicht funktionieren.
Also wenn man das so in Java aufruft,
dann gibt es, meckert der Compiler.
Er möchte gern, dass man hier noch einen generischen Typ Parameter angibt. Und da wir nicht wissen,
von welchem Typ das ist, also eigentlich sollte es sozusagen sagen,
das ist das klare Objekt für einen Titel oder für eine Titelliste oder was auch immer,
das wissen wir aber ja nicht,
deswegen machen wir hier so ein Fragezeichen dahin,
dann kann da tatsächlich was Beliebiges eingegeben werden.
Das heißt also, ich kriege hier ein Klassenobjekt von irgendeinem Objekt.
Und das erste, was ich hier mache, System Out Printaln,
irgendwie klasse und dann The Class get the name.
Also ich kann tatsächlich mir von der Klasse erstmal mit
Get Name den Namen geben lassen
Also das ist tatsächlich, was ich zur Laufzeit dieses Klassenobjekt
abfragen kann. Dann kann ich mir von der Klasse
die Interfaces geben lassen, die das Objekt implementiert.
Dann habe ich hier so eine private Methode geschrieben,
die nichts weitermacht, als die Interfaces irgendwie mit System Out rauszugeben.
Dann kann ich mir die Superklasse geben lassen,
da gibt es nur genau eine, Guettes Superclass.
Und dann habe ich hier so ein bisschen was gemacht,
dass ich gesagt habe, naja, wenn die Superklasse gleich ist,
ich habe hier gesagt New Object und dann Get Class.
Das heißt also, was ich da kriege, ist tatsächlich
die Klasse Object. Und wenn die nicht gleich sind.
Also wenn es nicht Object ist, diese Superklasse,
dann wird hier Erbt von ausgegeben. Das heißt,
alle Klassen, die nicht explizit von einer anderen Klasse erben,
erben ja von Java Lang Object,
hat das Chemie ein bisschen redundant zu sein, das auszugeben.
Also nur wenn von jemand anders geerbt wird, dann wird hier was ausgegeben.
Ja, das können wir uns auch gleich mal angucken.
Bei welchen von diesen Beispielen das rauskommt,
dann kann ich mir die Declaired Fields geben lassen,
also tatsächlich alle Felder, auch die Privaten.
Es wird einfach alles, also erstmal die Namen werden da rausgegeben letztendlich.
Nee, eigentlich eine, eben halt genau Field,
habe ich eben auch nicht so genau gezeigt.
Hier kommt eben halt Wenn ich sowas aufrufe wie Getty Claire Fields,
kommt da eine Ray von Feldern raus, Fields.
Also das ist eine Klasse, die als Metamodell ein Feld in einem Objekt modelliert.
Genauso wie hier ich Methode haben kann oder eben halt auch genau hier meine Klasse.
So, und die Sachen gebe ich halt hier alle aus,
werden hier alle irgendwie auszugeben und damit sieht man schon,
wie das ungefähr funktioniert.
Damit kann ich also die Informationen über so ein Objekt komplett rauslesen.
Ich glaube, was man nicht auslesen kann, sind die Namen der Parameter,
wo die Typen Wetter können mit ausgegeben werden, das kann ich mal zeigen.
Also wir haben ja hier, wenn wir das nochmal ausführen,
ich kann ja mir auch ein Exemplar von New Array, Titellisten, Test erzeugen.
Da habe ich hier tatsächlich so ein Objekt,
das ist dann irgendwie T1, der Test 1,
da links und auch bei dem kann ich jetzt sagen,
gib mir doch mal Show Info und dann gebe ich das Objekt hier rein und dann gibt ihr mir aus,
was da so reinkommt, also Array-Titellisten-Test,
ihr habt von Aha J. Unit Framework Test Case,
also tatsächlich, da ist man eine superklasse angegeben.
Privat ist irgendwie, es gibt hier so ein Array von
Titel und dann hat man einen Konstruktor, Parameter los,
muss man ja haben bei einer Testplatte und dann gibt es
hier ein Setup und dann gibt es den ganzen Testor-Operationen.
Das ist also die ganze Schnittstelle. Also auch da relativ unspektakulär,
das kriegt man an Informationen aus so einem Objekt raus. Was wir ja schon kennen,
ist, dass ich von einem Objekt testen kann,
ob es zu einer bestimmten Klasse gehört, das ist das,
was hier steht, also Object Instance of Someclass,
wobei das Klaas hier jetzt unglücklicherweise so aussieht,
als wenn es das Klaas wäre,
das ist ja wirklich ein Typ.
Übrigens auch das muss ich dazu sagen,
diese Diese ganzen Typen, Klaas, Field, Method,
sind alle definiert in Java Lang Reflect.
Es gibt also ein Paket in Java, in dem Sprachumfang,
dass solche Reflection-Mechanismen anbietet. Java Lang Reflect. Okay,
soviel dazu. Das heißt, was wir gesehen haben,
Java Besitz exemplarisch für eine Sprache mit Mob, unter anderem folgende Metaobjekte.
Klaas, haben wir gesehen, Package haben wir nicht gesehen.
Field, Lieferinformationen über Felder und erlaubt in Tronspektionen.
Method beschreibt eine Methode und erlaubt ihren Aufruf.
Haben wir auch jetzt mal gesehen, wie man sich
die Sachen so rausholen kann
Ständig sich das ganze Zeug so rauszuholen. Das ist wie so ein Datensack,
wo man sich die ganzen Sachen dann so rauszieht.
Also bei einem Klassenobjekt kann ich die Superklasse-Interfaces,
das haben wir alles eben schon in dem Beispiel gesehen,
nahmen alle Typen und Felder.
Und ich kann mir aber auch zum Beispiel hier...
Get de Claired Message aufrufen und kriege dann ein Array von solchen Methoden.
Ja. Okay. Jetzt ist der zweite Schritt. Das eine ist,
dass ich tatsächlich dynamisch zur Lautzeit irgendein Objekt habe und dann
mir Informationen über seine Klasse geben lassen kann.
Aber ich habe ja auch das Objekt selbst,
Da möchte ich ja vielleicht auch was mit machen können.
Ich möchte zum Beispiel ein Feld auslesen können an so einem Objekt.
Oder ich möchte vielleicht eine Methode aufrufen an diesem Objekt, über Reflection.
Wenn ich tatsächlich sowas wie einen Interpreter bauen will für Java,
dann muss ich tatsächlich, wie man tippt,
eine Kommandozeile ein, irgendwie MyObject.
Liste namen. Dann ist irgendwie der Interpreter das ja
umwandeln in entsprechende auch für solche Klassen und Objekte.
Und dann soll natürlich auch tatsächlich an so einem Objekt zur Laufzeit
diese Operation aufgerufen werden
Und dann ist es üblicherweise so, man kann das aufrufen,
auch die Kapselung zulässt. Also wenn ich öffentliche Methoden habe,
kann ich die aufrufen, private Methoden kann ich nicht ohne weiteres aufrufen.
Auch auf die Felder, wenn sie privat sind,
komme ich nicht einfach dran. Das ist erstmal nicht leicht
. Es gibt aber immer wieder so technische Aufgaben,
bei denen das, bei denen das notwendig wird.
Also wenn ich tatsächlich so Objekte serialisieren will,
da muss ich da irgendwie rankommen an die Felder.
Ein Ausgabe Garbage Collection, Debugging,
alles, was sehr mit Programmiersprachen selbst zu tun hat,
dann muss man immer so ein bisschen mehr können.
Also Introspektion, was kann man jetzt machen? Man kann sich erst mal von einer,
von einem Objekt das Klassenobjekt geben lassen und dann kann ich sagen,
C-Get, die klärt Field, dann habe ich erst mal nur das Feld.
Aber ich habe dann nicht das Feld selber,
sondern ich habe ja nur ein Objekt, das Feld,
ein Feld in dieser Klasse repräsentiert.
Also nehmen wir jetzt zum Beispiel mal an,
ich habe jetzt hier die Klasse Titel, die hat hier,
da habe ich mit Absicht hier dieses Ding nicht privat gemacht,
da bin ich da auch gut rankomme.
Das Ding hat also hier heißt Unterstrich Name. Wenn ich
also jetzt hier Get Declared Field Als String jetzt übergebe unterstrichen Name,
dann würde ich rausbekommen, ein Field,
aber es ist eben halt nur ein Exemplar von der Klasse Field.
Aber ich habe noch nicht das konkrete Objekt. Wenn ich
jetzt tatsächlich an einem konkreten Objekt genau dieses Feld irgendwie zugreifen will,
dann muss ich sagen, F, das ist dieses Field, Get.
Und dann muss ich eine Referenz auf das echte Objekt übergeben.
Also in diesem Fall hier irgendein Object.
Und dann kriege ich irgendein Value raus, ein Object tatsächlich.
Der Typ ist dann Object. Und dann kann ich das
auch setzen. Ich kann auch sagen, F.z,
und dann muss ich als ersten Parameter erstmal das Objekt übergeben,
an dem dieses Feld jetzt gesetzt werden soll.
Und dann kommt da der neue Wert rein
Wie gesagt, Voraussetzung ist entsprechend reduzierter Zugriffsschutz.
Das habe ich jetzt hier mal gemacht in diesem Beispiel.
Das werden wir uns auch gleich mal ansehen. Wenn man
das verändern will, dann kann man das tatsächlich verändern.
Ich kann tatsächlich irgendwie über Java Security Access Controller mir privilegierten Zugriff erschleichen.
Das ist relativ kompliziert. Das werde ich auch jetzt hier
nicht vorführen. Auf jeden Fall sollte man das sehr,
sehr vorsichtig machen, weil ja möglicherweise diese Kapselung nicht ohne
Grund so vorgenommen wurde.
Ich zeige jetzt hier nochmal
einen zweiten Aufruf
Das ist nämlich, das war jetzt hier alles Show-Info vor.
Da gibt es eben halt diese ganzen Operationen.
Ich gehe mal ganz nach unten, schwupps. Und dann
habe ich hier unten eine Operation, die heißt Vergleiche,
Aufrufe. Und was man da jetzt mal sieht,
ist, ich lasse mir von der Titelbibliothek in diesem,
in diesem kleinen Projekt ein Exemplar geben.
Und dann kann ich da sagen, gib zufälligen Titel.
Ich kann mir also einen beliebigen Titel geben lassen.
Und dann wird hier aufgerufen, an dem Titel Get Class.
Also habe ich hier wieder mein Klassenobjekt, CT.
Und jetzt kann ich hier aufrufen, Klassenobjekt,
Get Declaired Field,
unter Strich Name
Dann habe ich erst mal nur das Feld, also als Teil der Klassenbeschreibung.
Und dann kann ich sagen, hier Namefield, das ist genau dieses Field.
Get. Und jetzt übergebe ich als Referenz genau das Objekt,
nämlich das Titelobjekt, an dem ich dieses Namensfeld auslesen will.
Rauskommt ein Object. Und dann muss ich das jetzt hier
casten. Ich weiß, dass es ein String ist,
wenn ich mir das hier angucke, dann ist das tatsächlich ein String.
Also ich muss dann wieder mehr wissen,
dass es dann immer bei diesen Meta-Geschichten, ganz komisch.
Ich weiß irgendwie was, muss mir aber auch mal sehr sicher sein.
Und dann kann ich hier das auch wirklich ausgeben. Nee,
das werde ich hier nicht ausgeben, da wird einfach eine Leerzeile ausgegeben.
Und dann kann ich aber auch hier sagen,
gib mir mal die Methode, Keep Album.
Die habe ich also hier, die lasse ich mir geben als String.
Ich muss also sicher sein, dass es in dieser Klasse
eine Methode gibt, die gib Album heißt,
dann kann ich mir hier so ein Methodenobjekt geben lassen.
Und diese Methodenobjekte kann ich dann auch aufrufen.
Aber wir gucken erstmal sehen, also erstmal,
man kann hier sozusagen dieses Feld sich ausgeben lassen.
Wenn ich das jetzt Na, man sieht vielleicht erstmal was anderes.
Hier ist ein Try. Ihr wisst, wenn da ein
Try steht, dann muss auch irgendwo ein Catch folgen.
Vermutlich muss ich hier mehrere Catchblöcke haben,
weil da kann ja unglaublich viel schief gehen.
Könnt ihr euch überlegen. Also ich sage hier,
Get Declaired Field. Das kann sein, dass es
gar kein Feld gibt mit diesem Namen in dieser Klasse.
Kann schief gehen. Oder hier, geb Album,
vielleicht habe ich mich vertippt und dann steht da,
gib Album. Gibt es nicht, kann schief gehen.
Und dieses Meta-Optric-Protokoll von Java ist sehr restriktiv.
Es zwingt den Programmierer damit umzugehen. Das ist wieder sehr schön
hier zu sehen jetzt. Hier ist der eigentliche Code,
den gucken wir uns gleich noch genauer an.
Aber hier unten kommen jetzt
die ganzen Catches
Also es gibt eine No-Such-Field-Exception und das ist eine Checkt-Exception.
Ich muss sie behandeln. Also irgendwie out, vertippt beim Feldzugriff,
ist hier so mehr oder weniger das, was ich da sagen kann nur.
No such method kann halt auch schief gehen. In Vocation Target Acception,
wenn ich bei einer Methodenaufruf mit Reflection,
sage ich ja hier, Methode,
rufe mal auf und übergebe als erstes Parameter das Objekt,
auf dem ich die Methode aufrufen will.
Also genau andersrum, als wie das eigentlich gewohnt sind.
Kann es natürlich auch sein, dass es dann
eine Neilreferenz ist oder dass das gar nicht zueinander passt.
Ich habe eine Methode von einer anderen Klasse
und rufe die auf einem anderen Objekt auf
Beliebig viel falsch machen an der Stelle. Sämtliche Typüberprüfungen sind
komplett ausgeschaltet bei dieser Meta-Programmierung. Oder auch hier illegal Access Exception,
Zugriff auf ein privates Feldversuch. Was ihr jetzt gesehen habt,
ist, wenn ich das hier ausführe,
dann ist der Zugriff auch unterstrich Name gültig,
weil das ja hier im Paket zugreifbar ist.
Wenn ich das jetzt privat machen würde, dann würde es knallen.
Ich kann es jetzt mal zeigen. Also wir machen es
jetzt mal hier, machen wir es mal privat.
Übersetzt das hier. Hab dann hier das ganze Projekt,
übersetzt das
Aha. Ja, okay. Dann geht das jetzt erstmal nicht,
aber das soll ja auch gar nicht sowieso nicht ausgeführt werden,
dann mache ich das mal eben, kommentiert das mal eben aus.
So, das ist ja, ich will ja schon,
dass es da oben kracht, nämlich da,
wenn ich sage, Get, die klärt Field,
Name, das geht noch, aber wenn ich dann hier,
Name Field Get aufrufe mit dem Titel, dann kracht es,
weil ich dann an dem konkreten Objekt das aufrufe.
Also ich kann mir hier dieses Declared Field geben lassen,
kein Geheimnis, dass die dieses private Feld hat,
aber wenn ich jetzt an einem Objekt versuche,
diesen privaten Zugriffsschutz zu umgehen,
dann kracht es
Ich hoffe mal, dass es kracht,
dass ich mich jetzt nicht getäuscht habe.
Wenn ich jetzt hier dieses Ding aufrufe, vergleiche, aufrufe
, dann out. Zugriff auf ein privates Feld versucht.
Das ist also tatsächlich die richtige Exception, die da rauskommt.
Sehr schön soweit. Und dann gucken wir uns aber jetzt
auch noch an. Ich mache das jetzt wieder zugreifbar,
damit wir das Problem erstmal nicht mehr haben.
So. Und übersetze hier nochmal alles
Und dann sehen wir uns jetzt an, wie das ist,
wenn ich tatsächlich diese Methoden aufrufen will.
Auch da konnten wir uns erstmal den Quelltext nochmal an.
Also ich habe jetzt hier meine üblichen,
das kennt ihr ja schon, diese Messungen gemacht.
Also ich lasse mir hier zwei Methoden geben, gebe Album, gebe Titelnahme.
Das sind zwei Methoden, die diese Klasse-Titel hat.
Dann mache ich hier wieder meine Startzeit. Dann mache ich
hier eine Million Aufrufe, nämlich indem ich,
und das ist jetzt tatsächlich hier mit Reflection gemacht,
also eine Million Aufrufe per Reflection.
Also ich sage Namefield Set-Titel und dann setze ich da irgendwas
rein, was wirklich was anderes ist
Und ein paar Leute, die sind manchmal ganz schön schlau.
Die sehen irgendwie, ihr macht eine Million mal
den gleichen Aufruf und da ändert sich nichts am Objektzustand.
Dann optimiere ich das weg. Deswegen habe ich hier jetzt
so wirklich Set-Titel mit jedes Mal was anderem gemacht.
Dann rufe ich auf, gebe Album in Vogue-Titel.
Also tatsächlich, ich habe hier einen Methoden auf Objekt
und rufe auf invogue und zwar an genau diesem Objekt.
Und genauso gibt Titelnahme in Vogue-Titel. Das Äquivalent dazu steht hier
unten. Also diese Aufrufe hier, Name, Field,
Set, Titel, E-Plus-Plus, das ist das Gleiche,
wie, wenn ich sagen würde, titel.name ist gleich E.
Und auch hier, dieser Aufruf, gib Album in Vogue-Titel,
ist eigentlich nur Titel, gib Album.
Oder gibt Titel, gibt Titelname. Das ist die normale
Aufruf, das da oben ist über Reflection.
Und was ich einfach mal zeigen will, ist,
dass das hier möglicherweise länger braucht als das hier.
Das könnt ihr euch überlegen,
dass so dieser Mechanismus über Reflection möglicherweise
nicht ganz so effizient ist
Vielleicht ist es ja auch schneller, wer weiß das.
Wer ist denn der Meinung, dass das langsamer sein wird?
Wer vermutet, dass es langsamer sein wird?
Reflection. Ja, so ein paar. Glaubt jemand,
dass sich Inflection schneller ist? Ich glaube,
dass jemand, ich vermute mal eher nicht.
Ja. Wir können das ja mal ausführen.
Das ist immer ganz lustig, das mal auszuführen.
Ich liebe das immer. Ich sage jetzt hier,
vergleiche Aufrufe. Und es kommt raus,
eine Million Aufrufe per Reflection,
1.2200 Millisekunden versus 68 Millisekunden für
ohne Reflection
Ich kann das auch noch mal noch mal machen,
weil das ja oft verfälscht.
Also machen wir noch mal Vergleiche, Aufrufe.
Es ändert sich nicht viel. Also hier eine SIT hier,
das ist schon, also ich hatte zu Hause weniger Unterschied.
Das war so Faktor 5 bis 10 mal schneller.
Das geht ja hier schon in Richtung Faktor 20.
Also tatsächlich deutlicher Unterschied. Ja. Okay,
also das auch noch mal als Erinnerung daran,
wenn man jetzt dynamisch sowas aufruft,
dann kann man
das machen
Man ist dadurch eben halt sehr flexibel. Man kann tatsächlich an alles
ran, aber es ist auch nicht ganz so effizient.
Hier ist es noch mal beschrieben, also Object Get Class,
dann lasse ich mir die Methode geben.
Ich kann natürlich auch dann noch mir andere Methoden geben lassen mit Parametern.
Also wenn ich hier die Signatur genauer angebe,
nicht nur den Namen,
sondern auch noch dann die entsprechenden Typparameter für die Parameter-Typen,
dann kann ich da auch dann andere Methoden auf und kann die auch aufrufen.
Also hier auch tatsächlich mehrere Argumente übergeben.
Und ich kann auch tatsächlich per Reflection
Exemplare erzeugen von Klassen
Gut, das ist das, was in Java geht.
Das in Java ist eigentlich relativ konservativ. Das heißt,
man kann sich von einer Klasse Informationen geben lassen,
welche Methoden hat denn die Klasse, welche Felder hat sie,
welche Konstruktoren, von welchen Klassen erbt sie,
welche Interfaces implementiert sie und ich kann von einem konkreten,
ich kann an einem konkreten Objekt dann tatsächlich auch diese Operation aufrufen,
kann Felder auslesen, wenn das denn zugelassen ist,
kann möglicherweise diesen Zugriffsschutz noch umgehen,
aber es ist eigentlich genau das,
was auch in der normalen Programmierung geht
In anderen Meta-Object-Protokolls, ist das durchaus mächtiger. Also zum
Beispiel ist es in Smalltalk möglich, dass ich über Reflection
tatsächlich an das Klassenobjekt rankomme und dann auch den Quelltext der Klasse habe.
Und an diesem Quelltext dann irgendwie Dinge ändern kann.
So dynamisch, zur Laufzeit, den Quelltext einer Klasse verändern.
Da wird es dann langsam, wow, da wird es
dann interessant, wenn sozusagen Codes sich selbst modifiziert.
Lange Zeit war das so, oh, das ist cool,
das ist die Möglichkeit, künstliche Intelligenz zu implementieren,
in Softwaretechnikkreisen gilt das eher als Todsünde,
dass man versucht,
den Quelltext wieder selbst zu verändern
Deswegen ist Java als eher softwaretechnisch ausgerichtete Sprache auch eher konservativ.
Aber in anderen Sprachen ist das durchaus mächtiger. Es gibt auch sowas wie Lisp,
es gibt das Common Lisp Object System, Cilos.
Das mal irgendwann vor die Finger bekommt, einfach mal angucken.
Auch ein sehr spannendes Meta-Object-Protokoll. Also es gibt dann auch einfach Sprachen,
in denen man dann vor und nach jedem Aufruf einer Methode
irgendetwas zusätzlich tun kann.
Also der Zugriff wird dann irgendwie durch so eine Metro-Methode ermöglicht.
Also ich kann jeden Methodenaufruf umlenken und dann kann ich tatsächlich
irgendwie dieses Protokoll
beliebig verändern
Oder eben tatsächlich, ich kann die Methode komplett ersetzen,
kann die Methoden-Implementation ändern. Das sind dann sehr, sehr mächtige Konzepte,
die auch einfach eine ganz andere Art der Programmierung ermöglichen.
Ja, also, das haben wir jetzt eigentlich jetzt nochmal so zusammengefasst gesehen.
Das ist das, was wir mit dem Java-Mode machen können.
Ich kann also feststellen, welchen Typ hat ein Objekt,
von welcher Klasse er abti und so weiter und kann eben
halt auch Manipulationen machen, so in Grenzen.
Ich kann also Objekte erzeugen, ich kann auch schon aufrufen,
ich kann auch Attribute auslesen und gegebenenfalls setzen.
Gibt es noch Fragen jetzt zum Meta-Object-Protokoll von Java
Ja, da ist eine Frage. Ich habe es nicht verstanden
, musst du lauter sprechen? Er fragt, ob ich
das Projekt auch zum Download anbieten kann, kann ich machen, ja.
Also das ist ja nur so ein bisschen Reflection-Zeug da auslesen,
muss man da nicht selber abtippen, kann ich verstehen,
wenn ihr das dann mal noch weiter ausbauen wollt,
könnt ihr das machen, ich kann das dann mit ins Netz stellen gleich.
Gibt es sonst noch Fragen? Keine, dann vielen Dank
für heute