So meine sehr gefährden Damen und Herren, ich freue mich
sehr, dass ich Sie heute alle hier begrüßen darf.
Mich haben Sie hier noch nicht gesehen,
auch wenn Michael Kritzer letzte Woche wohl angekündigt hat.
Mein Name ist Matthias Riebisch, Sie sehen mich hier.
Ich habe mich mal einfach mehr erlaubt, hier mich hinzustellen,
weil ich auch ein bisschen an den Folien was geändert habe.
Die anderen sind die Herren, die vor mir an diesen Folien
rumgewerkelt haben. Wir haben heute und in der nächsten Woche
das Vergnügen miteinander und ansonsten freue ich mich,
dass ich sie dann im April bei SE2 im Wechsel mit
Herrn Kritzer ansehe bzw. so sie mögen
Das ist ja nicht für alle von Ihnen Pflicht in der
Veranstaltung Softwaretechnik im vierten Semester, da so ein Jahr drauf.
Ich bin, nur um mich ganz kurz vorzustellen,
ich bin seit fast vier Jahren mittlerweile an der Uni Hamburg,
vorher war ich an der TU Ilmenau, mein Arbeitsgebiet ist Software Engineering,
also die Technologie, wie sollte Software entwickelt werden.
Wir haben, Entschuldigung, ich bin leider ein bisschen erkältet,
wir immer versuchen, das Mikro ein bisschen abzuschirmen davor,
damit ich die Leute am Stream da Herzkasper kriegen.
Ja, mein Gebiet ist also die Art und Weise, wie Software entwickelt wird.
Und insofern hat das ganz viel damit zu tun,
wie man dann arbeitet und die Basics,
die Basis dafür liegt natürlich die Programmiersprache und das ist das,
womit wir uns hier beschäftigen. Vielleicht zu meinem Hintergrund noch,
ich bin ursprünglich gar nicht Informatiker gewesen vom Studium her,
sondern habe ein Ingenieurstudium gemacht und dann aber schon direkt während des Studiums habe ich Computer so fasziniert,
dass ich eigentlich ganz viel nebenbei gemacht habe und dann viele von ihnen arbeiten,
vielleicht als wissenschaftliche Hilfskraft,
so habe ich dann irgendwann angefangen und Promotion war deine Informatik und danach in der Industrie ein bisschen Informatik,
Softwarearchitekt und Projektleiter.
Und dann bin ich irgendwann vor zehn Jahren 14 Jahren mittlerweile wieder an die Universität zurückgegangen.
Ja, wir beschäftigen uns heute mit Interfaces und mit Testen.
Zunächst erst mal, worum geht es? Wir haben schon
gehabt, dass es Klassen gibt. Sie haben schon von Typen gehört,
die Typen spielen heute noch eine Rolle und Operationen waren auch schon mal dabei.
Wir wiederholen oder was nochmal wichtig ist, ist,
dass diese Klasse zwei zentrale Eigenschaften hat. Sie definiert den Typ,
den Typ kennen Sie schon vom Datentyp her,
sowie diese einfachen Typen wie Intager und Kleidkommazahlen,
die real oder Zeichenketten, String.
Ja, also diese Typen, so ähnlich definiert die Klasse,
ja auch ein Typ und es gibt eine Implementation und wir beschäftigen uns heute mit dem Interface,
was ausschließlich den Typ beschäftigt, aber keine Objekte erzeugen kann.
Ja, worum das Ganze? Ja, also wir sollten
ja immer ein bisschen wissen, warum machen wir das?
Warum haben diese Leute, die die Sprache Java entwickelt haben,
denn sich das ausgedacht? Und es geht doch dabei,
es geht darum, Arbeitsteilung zu organisieren.
Es geht darum, dass wir Software so entwickeln können,
dass wir in mehrere, in größeren Gruppen zusammenarbeiten können.
Jetzt komme ich wieder vom Software Engineering Seite her,
dass wir Aufteilen können die Arbeit zwischen die eine Gruppe und
die andere Gruppe und die dritte Gruppe.
Und tatsächlich können diese Leute parallel arbeiten. Ja,
wenn wir Software entwickelt, weiß das,
dann nimmt der Produkte, die jemand anders vorher entwickelt hat,
nimmt er Softwarepakete, die jemand anderes entwickelt hat.
Und dieses arbeitsteilige Verhältnis kann man ganz gut als Clearent und
Dienstleister beschreiben, wie das diese Folie zeigt.
Und wir wollen genau dieses arbeitsteilige Verhältnis,
dieses nette Handshake hier, zeigt,
dass dieses arbeitsteilige Verhältnis soll die Software unterstützen und das ist das Ziel,
wo wir hinkommen wollen.
Ja, wir trennen Aufgaben voneinander, es steht auf dieser
Folie und die Arbeitsteilung, die hat einen bestimmten Ziel.
Diese Arbeitsteilung hat das Ziel, panterbare Projekte zu erzeugen,
aus riesengroßen Projekten. Wenn Sie 30 Leute zusammen haben,
dann haben Sie diesen Effekt vom Berliner Flughafen oder von der Elbphilharmonie,
Projekte, die scheitern oder die ganz spät fertig werden.
Wir haben ganz häufig Softwareprojekte, die viel zu groß sind,
als dass man sie erfolgreich handeln kann.
Man muss Ihnen eine Struktur geben, man muss Arbeitsteilung geben.
Diese Arbeitsteilung braucht man,
damit man sich nicht ständig von früh bis spät absprechen kann,
damit man auch mal einen Teil bei einer anderen Firma unterentwickeln lassen kann.
Bis dahin, dass viele Firmen Aufträge eben nach Indien oder China oder Rumänien
geben wollen. Das geht nur mit klar definierter Arbeitsteilung.
Über diese Arbeitsteilung rede ich gerade und diese Arbeitsteilung kann man
nach verschiedenen Richtungen nach verschiedenen Gesichtspunkten gliedern.
Einen so einer Geschichtspunkt könnte die Spezialisierung sein, die wir
ganz oben sehen. Was wir dabei aufteilen wollen,
ist, dass wir das nach Wissensgebieten aufteilen.
Also Arbeitsteilung ja, aber nach Wissensgebieten. Sowas kennen sie
schon. Sie kennen schon solche Sachen, dass man in bestimmten Bereichen sowas macht.
Ich habe jetzt hier mal Vorteile schon aufgeschrieben. Ja,
es gibt, man möchte hier die Arbeitsteilung müsste ich zum Beispiel in Abläufe in der Produktion hinkriegen,
man möchte hinkriegen, dass man Leute parallel arbeiten lassen kann,
damit es bei hoher Last besser geht oder dass man einer verantwortlich ist für rechtliche Regelung,
das ist auch wieder ein Spezialist oder ein anderer Spezialist,
der für die Datenspeicherung zuständig ist.
Es ist bald Weihnachten, deswegen lassen Sie uns mal was anschauen,
ein Gebiet, wo wir das zu tun haben könnte und das ist der Paketversand.
Sie wissen alle, bei der gelben Post,
das Step, der Bär in den Wochen vor Weihnachten,
das wird jetzt vermutlich schon losgegangen sein und ich möchte deswegen das als Beispiel mal einführen,
ja, also das, was wir hier als ersten Punkt haben,
Abläufe in der Produktion, ist bei der Post dieses Paketes sortieren, Pakete weiter befördern.
Da gibt es einen Sammelplatz, da wird das verteilt,
in welcher Reihenfolge fährt wer.
Das sind die Logistikabläufe. Und die brauchen natürlich bei Hochlast
mehr Leute. Das ist wieder ein Spezialist. Ein Spezialist,
der sich darum kümmert, Saisonkräfte zu beschäftigen.
Vielleicht arbeitet die man von ihnen in dieser Weihnachtszeit als Saisonkraft.
So, dann gibt es irgendwas mit rechtlichen Regelungen.
Das spielt bei der Post nicht so eine große Rolle,
aber sicherlich auch. Dafür wird es sicherlich einen Spezialisten geben.
Und das Gleiche, die müssen vielleicht die Daten archivieren,
um sie, das macht auch wieder ein Spezialist.
Okay, gehen wir mal zu einem Informatikbeispiel.
Und zwar, also Wie hat das bei Rechnung zu tun?
Mit Rechnern könnte das zum Beispiel was zu tun haben,
mit Workflows, Workflow-Systeme, also wo man Arbeitsabläufe flexibel gestalten kann,
sind auch ganz häufig anzutreffen.
Und auch da haben wir Spezialisten. Und die Arbeitsteilung könnte
zum Beispiel sein, einer ist für die Arbeitsabläufe verantwortlich.
Der kann das am besten, der kennt die Arbeitsabläufe im Unternehmen ganz gut.
Das ist ein spezielles. Der nächste Spezialist kennt sich mit Hochlast aus.
Hier wäre das bei Webservern, ich habe mehrere Webserver parallel,
bei Amazon sind das wahrscheinlich hunderte oder tausende oder bei Google
und schon bei einer kleinen Firma, die haben mindestens drei,
vier, fünf Stück.
Und dieses Load-Balancing aus Fine-Dunen,
das kann auch nur ein Spezialist
Gibt es die rechtlichen Regelungen? Wann nehme ich ein Paket,
wann nehme ich welchen Paketdienst, wann erlaube ich Zahlung per Rechnung?
Auch das muss, da gibt es einen Spezialisten für.
Und das nächste Datenbanken, ganz klar, kein Informatiker wird
die Datenbank selbst entwickeln, die nimmt man vom Spezialisten.
Oder wenn es ein Problem gibt, fragt man auch jemanden.
Also das ist nur um diese Arbeitsteilung ein bisschen zu illustrieren,
das geht darum. Und der zweite Punkt,
den habe ich gar nicht weiter ausgeführt hier,
Arbeitsteilung in kleinere Einheiten, einfach nur,
wenn wir eben mit Datenspeicherung zu tun haben und das gibt 17 mal Datenspeicherung,
warum kann man das nicht in fünf Pakete zerlegen,
dass fünf Leute parallel arbeiten können
Und was auch noch wichtig ist, wenn das,
ob es, das war zu schnell,
so viel wollen wir nicht mit einmal.
Wir wollen hier, das sollte nur der Leserstrahl werden,
wir wollen hier eine Abgrenzung von Arbeitsaufgaben machen.
Diese Abgrenzung brauchen wir deswegen, damit die Leute unabhängig voneinander
arbeiten können. Das ist der eine Punkt, ja,
aber hier gibt es noch ein paar mehr Punkte und die habe ich hier aufgeschrieben.
Wenn wir eine Abgrenzung zwischen A und B machen wollen,
dann bedeutet das zum ersten, man muss sich darauf verlassen können,
was der andere macht,
ohne dass der da ist
Das nächste ist, ich muss es gar nicht verstehen,
wie der andere das genau macht. Ich weiß einfach,
er macht es so und so, das kommt raus.
Das vereinfacht meiner Arbeit sehr stark. Das ist auch wichtig,
um große, komplexe Aufgabenstellungen zu beherrschen. Und das Letzte,
wenn wir etwas Kleines ändern, wenn also B,
etwas ein bisschen optimiert oder einfach etwas ändert,
dann bin ich nicht betroffen.
Das bedeutet, und das hat zur Folge, soll zur Folge haben,
dass die Auswirkungen von Änderungen auf kleine Bereiche begrenzt bleiben.
Nicht, ich habe dort was Kleines zu ändern und an
tausend Stellen muss ich jetzt was anpassen,
weil das zu viel Fehler bringt.
Das Ganze nur zur Motivation,
wozu brauchen wir diese Arbeitsteilung
Etwas, was sie vielleicht schon gehört haben, auf jeden Fall
noch weiter hören. Es gibt bestimmte Entwurfsprinzipien für Software.
Und diese Entwurfsprinzipien, die sind hier schon sichtbar geworden.
Ich will sie nur kurz noch mal nennen, ja,
Modularisierung, ich schaffe solche Einheiten, Kapselung,
das war diese Abgrenzung, die hier unten steht, ja,
Information Hiding, das heißt, das ist hier der zweite Punkt.
Ich muss nicht verstehen, wie jemand anderes etwas erfüllt.
Es reicht mir, es zu wissen, dass es passiert.
Und Separation of Concerns spielt jetzt hier noch keine Rolle,
Trennung von Zuständigkeiten, dass ich Module nach Spezialisierung bilde.
Das war der obere Punkt ein bisschen.
Okay, lassen Sie
uns weitermachen
Diese Arbeitsteilung, wir kommen wieder zurück zu den Dienstleistungen,
die wir eben haben, machen wir über eine sogenannte Schnittstelle.
Schnittstelle, ein Interface hat also nichts mit Schere zu tun,
sondern es ist die Stelle,
wo wir die zwei Sachen einander berühren lassen, wo der Übergabepunkt ist.
Und dieser Übergabepunkt sieht so aus, dass wir Methoden haben.
Wir sind ja im Objektorientierten, das heißt, die Objekte sind über Methoden erreichbar.
Die Klassen definieren diese Methoden, das haben sie alles schon gehört.
Und wenn wir das jetzt mal so darstellen,
wir haben wie vorher Klient und Dienstleister,
dann vor der der Klient hier etwas an und was er dort anfordert,
sind die hier von dem Dienstleister angebotenen Implementierungen.
Bei dieser Gelegenheit ist immer ganz nett mal so ein Begriff anzunehmen,
Begriff des Tages mache ich immer ganz gerne,
weil es gibt immer so in der Informatik viele Begriffe,
die man irgendwie immer verwendet und schon ganz oft gehört hat,
aber so ganz sicher ist man doch nicht, was die bedeuten.
Heute, der Begriff Implementierung, was heißt denn Implementierung?
Also sie müssen es nicht ganz exakt sagen.
Es reicht schon, wenn jemand eine Vermutung hat.
Bitteschön. Ihr werdet der Aufgabe kommt. Die konkrete Lösung
ist der Vorschlag. Das ist schon mal sehr gut.
Vielen Dank. Gibt es noch weitere Erläuterungen? Bitte?
Die Realisierung einer Aufgabe, das klingt auch sehr gut.
Noch ein Vorschlag. Lösung und Realisierung liegt ja dicht beieinander
Keiner mit dem Vorschlag, das heißt Quellcode entwickeln.
Das freut mich sehr. Das ist nämlich der typische,
die naive Antwort des Informatikers, weil man benutzt im Informatikerslang,
im Programmiererslang, benutzt man Implementieren gerne dafür, den Quellcode schreiben.
Und das ist nicht richtig. Tatsächlich heißt es, verwirklichen.
Ja, so ist der Gebrauch in vielen anderen Disziplinen.
Also zum Beispiel, wieder zu dem Beispiel mit dem Paketdienst
zurückzukommen, der Paketdienst denkt sich aus,
so und so sollte er mit den Paketen gearbeitet werden.
Das machen in Wirklichkeit Menschen
Und diese Arbeit mit den Menschen, da kann man sich
ein Konzept ausdenken. Und wenn man dieses Konzept implementiert,
dann hat das nichts mit Quellcode zu tun.
Dann hat es damit mit Schulungen zu tun und mit vielleicht Arbeitshilfen und Transportwagen.
Aber man kann es auch implementieren, dieses Konzept.
Also Implementierung merken wir uns als Verwirklichung. Und was wir
hier haben, hier kommen wir wieder zu unserer Implementierung,
der Dienstleister bietet diese Schnittstelle an.
Das ist das, was wir auf der vorgehenden Folie haben.
Man kann sich darauf verlassen, dass diese Aufgabe erfüllt wird.
Und diese Dienstleistung hat dann tatsächlich ein Implementierung dahinter
Erreichbar ist das Ganze über die Schnittstelle. So,
und jetzt brauchen wir wieder diese Abgrenzung,
die uns hilft, dass Änderungen nicht so weit ausbreiten,
dass die Aufgaben nicht so kompliziert sind.
Eben diese Entwurfsprinzipien und das ist durch die Kapselung erreichbar.
Kapselung ist etwas zunächst, was die Programmiersprache uns ermöglicht.
Das ist nett bei fortschrittlichen Programmiersprachen,
gibt es diese Kapselung vor 30 Jahren bei den Programmiersprachen,
gab es das leider noch nicht.
Und deswegen ist das schon tatsächlich ein Gewinn und diese Kapselung
schützt vor äußerem Zugriff
Der naive Betrachter mag sich fragen, wozu denn das?
Wieso denn schützen? Da gibt es noch keinen Bösen.
In der Tat hilft es, wenn man die Informationen verpackt,
dass man sie von außen nicht erreichen kann,
dann verlassen sich die anderen nicht darauf. Wenn ich einen anderen,
einen Benutzer, wenn der Klient sich darauf verlässt,
ach intern wird das mit einem Array gemacht, dann macht er irgendwelche Sachen,
die darauf beruhen, dass da ein Array benutzt wird.
Und wenn man das dann optimiert zu einer Liste,
dazu kommen wir nächste Woche Arrays und Listen,
wenn man sich das dann optimiert, einfach nur,
weil es schneller geht, dann muss der Klient das ändern,
obwohl es sich an der Schnittstelle gar nichts geändert hat.
Also dieses Kapseln hier mit Public und Private, was sie ja schon kennen,
das ist ein Vorteil, den wir hier haben.
Hier unten steht das nochmal, das Ausblenden von Details
vereinfacht das Ganze und man kann diese Änderungen leichter machen.
Wie sieht das nun mit der Klasse aus?
Die Klasse ermöglicht genau das. Die Klasse ermöglicht,
dass es Operationen gibt und diese Operation kann man aus Sicht
aus Sicht der Klasse, kann man die aufrufen.
Das ist das Interface. Dann können wir den Sachen
Werte übergeben. Das sind die Parameter und die Ergebnisse.
Das steht hier oben. Also die Parameter übergeben wir,
das Ergebnis bekommen wir zurück
Ja, und wir haben noch ein bisschen was, hoffentlich stehen dort Kommentare dabei.
Das ist erstmal nicht weiter als die Sachen, die an der Schnittstelle stehen.
Das ist die Information, die ich als Klient brauche.
Die Implementierung brauche ich eigentlich nicht. Und manchmal ist die
auch für mich gar nicht sichtbar, ist versteckt.
Die Implementierung brauche ich natürlich noch, dass in dem Methoden Rumpf,
also in dem Teil dann nach der Implementierung kommt, steht Programm,
steht Java-Code und ich habe tatsächlich noch dort temporäre Variablen,
Hilfsmethoden oder was auch immer.
Und das können wir deutlich trennen in die Außenansicht und die Innenansicht. Ja,
und das ist die Trennung, hier hat die Überschrift Schnittstelle und Implementierung,
das Korrespondiert hier mit der Außenansicht,
das ist die Schnittstelle und hier Implementierung ist das Innere,
das finden wir jetzt auf der nächsten Folie.
Und für die Trennung sollte für den Klienten, solltet die Innenansicht
völlig uninteressant sein. Er muss sich das nicht angucken.
Er kann vor dieser Information auch geschützt werden. Damit wird es
für ihn vereinfacht und das soll es auch gar nicht wissen,
damit wir es ändern dürfen, ohne dass er auch eine Änderung vornehmen muss.
Und diese Unterscheidung, diese Trennung ist eine ganz wichtige,
fortschrittliches Merkmal von heutigen Programmiersprachen.
So, wir haben das schon mal irgendwo gesehen.
Sie haben das bei Blue Jay bei Fred gesehen schon mal,
dass es da der Etitor das anzeigen kann,
die Schnittstelle oder die Implementierung oder diese API,
Application Program Interface, dass wir dort nur die Schnittstelle sehen.
Und die Schnittstelle sagt uns erstmal nichts wie die Implementierung
aussieht und das ist die Konsequenz, hat hier unten,
was hier ganz unten steht,
die gleiche Schnittstelle kann auf verschiedene Weise implementiert werden
Nun ist die Frage, wie trennen wir das voneinander?
Wir haben schon gesagt, Schnittstelle, Implementation sind gut,
ach so, hier noch was zu den Entwurfsprinzipien.
Also, dass die Entwurfsprinzipien diese Trennung,
ist vor allen Dingen Kapselung und Information Hiding.
So, kleines Beispiel dazu. Wir haben,
jetzt habe ich irgendwas kaputt gemacht, gibt es noch.
Gut, ein einfaches Beispiel mit dieser Klasse Konto.
Das ist immer was, was ich hier da vorstellen kann,
zumal wenn das Konto nur solche einfachen Sachen hat,
wie einzahlen,
auszahlen und
den Saldo anzeigen
Ja, und das schauen wir uns in Java mal so an,
dass wir jetzt eine einfache Implementierung haben, also eine Verwirklichung,
wo wir eine lokale, eine Exemplarvariable haben,
die intern den Saldo hält, den Kontostand hält.
Und die Einzahlung natürlich das verändert. Und wir uns anschauen,
wie das aussehen kann. Und wir haben uns veranschaulichen können,
dass das auf unterschiedliche Weise gemacht werden kann.
Also es sollte aber für den Kunden,
in dem Fall könnte das auch der Kunde der Sparkasse sein oder der Bank sein,
der Kunde, der Anwender dieses Dienstes,
für den sollte das keine Auswirkungen haben, wie wir es intern implementieren.
Schauen wir uns das hier mal an, so könnt ihr das in Java aussehen.
Und das ist hier die sogenannte benannte Schnittstelle. Das Interface Konto,
ja, wir haben die geschweiften Klammern, kündigen an,
da ist ein Block und ist im Block stehen drei Methoden.
Das kennen Sie schon, die Signatur einer Methode.
Da steht also der Rückgabewert, der Name der Methode und
die Parameter. Und wir haben einen Wert,
den wir einzahlen oder einen Wert, den wir auszahlen.
Da gibt es jeweils einen Parameter Und bei der Ausgabe
des Saltos gibt es keinen Parameter, den wir eingeben.
Dafür gibt es einen Rückgabewert. Aus Einfachheitsgründen haben wir mal hier ganze Zahlen.
Komma, hier gibt es keine Centbeträge bei unserem Konto.
Das ist völlig okay. Wir machen das ja auch
ein bisschen vereinfacht. So könnte das aussehen,
aber wenn Sie jetzt denken, das ist doch eigentlich komisch,
so eine Klasse habe ich noch nicht gesehen, dann ist das auch richtig.
Sie sehen ja hier oben, fehlt das Schlüsselwort Glas.
Hier haben wir das Innenschlüsselwort Interface. Es sieht also irgendwie aus wie eine Klasse,
es ist aber keine, und zwar ist das eben dieses
Sprachkonstrukt Interface und das heißt offiziell benannte Schnitt Mitstelle named Interface,
weil das Schlüsselwort aber Interface ist, hat sich das so eingepürkelt.
Die Leute sagen immer nur Interface, aber eigentlich ist es named Interface. So,
was uns auch noch auffällt im Unterschied zu einer Klasse,
normalerweise führt er ja nach der Bezeichnung, nach der Klassensignatur,
nach dem Name und Rückgabewertung, Parameter wird das Gehirn,
geschweifte Klammer auf, dann kommt der Quellcode dieser Methode,
geschweifte Klammer zu, dann geht es,
dann ist es zu Ende.
Das fehlt hier. In der Tat ist es so,
diese benannte, dieses benannte Interface hat keine Methodenimplementierung.
Und das ist genau der Punkt, wo wir darauf hinwollten,
wir trennen Implementierung von der von der Schnittstelle.
Hier haben wir nur die Schnittstelle nichts weiter. Und das ist für den Zweck,
den wir eben hatten, bessere Arbeitsteilung, bessere Trennung.
So, schauen wir uns das etwas genauer an,
was das für, das wollte noch nicht hier,
was das Ganze für Eigenschaften hat.
Was sind solche Interfaces? Tatsächlich sind die Interfaces nur die
Methodenköpfe mehr nicht. Keine Implementierung dazu. Und kein,
also das ist wie diese Implementierung, wenn die Methoden Rümfer
zusätzlich gibt es keine Felder. Das hat natürlich zur Folge,
wenn es gar keinen Quelltext für Methoden gibt,
dann kann ich auch keine Objekte daraus instanzieren,
damit instanzieren.
Das heißt,
es gibt hier keine Exemplare von Ihnen
Damit ich aber überhaupt etwas Lauffähiges daraus bekomme,
ist der letzte Punkt wichtig. Sie können durch Klassen
dann realisiert werden, implementiert werden, verwirklicht werden.
Und es gibt noch einen kleinen Randnotiz für die Fortgeschrittenen,
das brauchen wir aber nicht, uns so direkt zu merken.
Wir schauen uns an, wie das durch eine Klasse implementiert wird,
implementiert jetzt als Verwirklichung, die Klasse hat tatsächlich die Verwirklichung,
die Schnittstelle oben gemacht.
Und zunächst muss die Klasse erst mal sagen, ich mache das.
Hier schauen wir uns gleich das Beispiel an.
Wir hatten vorher eben dieses Konto, ich gehe mal kurz zurück.
Ja, also hier war dieses Interface-Konto,
was nur diese drei Methoden benannte
Weiter. Hier steht jetzt, gibt es dieses Schlüsselwort,
was uns jetzt neu ist, Implementskonto und die Klasse,
die wir jetzt dieses tatsächlich macht,
die tatsächlich die Implementierung dazu auch enthält, die heißt,
er hat jetzt einen anderen Namen bekommen natürlich, konto simpel.
So, die hält den Kontostand hier in dieser Variable und
dann hat sie wieder diese Methoden und tatsächlich hier gibt es geschweifte Klammer auf,
Quelltext dieser Methode, geschweifte Klammer zu.
Ja, also hier gibt es jetzt tatsächlich eine Implementierung dieser beiden Sachen und das ist,
hat die Auswirkung Dass das Interface den Entwickler verpflichtet,
das und das zu implementieren.
Derjenige, der das Interface schreibt, der stellt sozusagen,
gibt dem anderen eine Verpflichtung mit. Sie kennen noch nicht den Begriff Vererbung,
der wird aus den Kürze kommen, aber bei einer Vererbung hat man,
wenn man eine Abschafft antritt, hat man ja auch manchmal eine Verpflichtung.
Also da gibt es in irgendwelchen Märchen, also die Königstochter,
die musste erst heiraten, bevor sie das Schloss bekommt oder irgendwelche Verpflichtungen.
Hier ist auch eine Verpflichtung, es geht nicht um Erben,
hier geht es um die Verpflichtung,
nämlich genau die Schnittstellen komplett zu implementieren,
die diese Klasse Konto hat
Und das wird tatsächlich vom Laufzeitsystem der Programmiersprachumgebung überwacht,
dass sich tatsächlich nichts vergesse davon. Und damit ist das ein Mittel,
Arbeitsteilung zu organisieren zwischen verschiedenen Entwicklern. Der eine sagt,
das und das ist das, was ich haben will und kriegt,
der andere kriegt die Aufgabe und da kommt Pala überwacht schon,
ob er das richtig macht, ob er tatsächlich das Schnittstelle,
die Schnittstelle so implementiert.
Möchte ich noch eine Randnotiz geben, hier ist kein Test dabei.
Ja, also nur wirklich nur, ob die Schnittstelle so aussieht.
Was hat das Ganze für Auswirkungen auf den Klienten,
der das Ganze benutzt
Wir wollen zunächst erstmal das Trennen in zwei Punkte.
Wir wollen Objekte erzeugen und wir wollen Objekte benutzen.
Wir fangen mal mit der Benutzung an. Das ist ja das,
was uns am meisten interessiert. Wir wollen tatsächlich ein Konto eröffnen.
Wir wollen ein Konto einzahlen, wir wollen auszahlen.
Wie sieht das damit aus? Zunächst ändert sich gar nichts. Der Klient,
der kennt das Interface und er weiß, ich kann einzahlen,
ich kann auszahlen, ich konnte den Kontostand abfragen.
Das ist das, was er wollte und das bekommt er. Das wird also so aussehen,
wenn ich hier zum Beispiel den Überweiser habe, das wäre der,
der das benutzt, der hat irgendwie einen Konto Queller,
ein Kontoziel und Bei der Quelle zahlt er etwas aus,
beim Ziel zahlt er etwas ein,
damit benutzt er zwei Konten.
In dem Fall heißen die Konten, heißen hier Quelle,
das ist das eine.
Und Ziel ist das andere. Diese beiden konnten,
benutzt er, um diese Überweisung zu machen, ist tatsächlich sehr vereinfacht.
In Wirklichkeit wird er hier ein bisschen mehr passieren,
aber das reicht ja und damit kann man das erstmal machen,
ohne wirklich diese Implementierung zu kennen.
Was ist der Kernpunkt, das ist das,
was wir mit Arbeitsteilung erreichen wollen
So, wie sieht das nun aus, wenn ich diese Objekte erzeuge?
Also wenn ich in unserem Beispiel ein Konto eröffnen möchte,
spielt dann das Interface irgendeine Rolle,
wirkt sich das auf unsere Arbeit aus,
tatsächlich dann wirkt es sich ein bisschen aus,
weil von dem Interface selber kann ich kein Exemplar ableiten.
Ich kann nicht direkt eine Instanz erzeugen, ein Exemplar,
sondern ich muss tatsächlich immer eine Klasse angeben. Ja,
also hier, wenn ich Konto 1 erzeuge oder hier oben wäre es Quelle gewesen,
wenn ich Konto 1 erzeuge, muss ich angeben,
wie die Klasse heißt.
Und wenn ich überweiser mache, dann ja,
hier zum Beispiel, da muss ich eben auch angeben,
dass das Konto 1 und 2 ist.
Also hier gebe ich eine neue Klasse und überweise an.
Das wäre die hier oben. Und ich benutze dann Konto 1 und Konto 2,
um hier 50 Euro von dem einen auf das andere zu überweisen.
Der Unterschied ist, hier taucht nirgendwo bei dem,
hier taucht nicht das Konto auf.
Konto taucht nur hier auf. Das war das Interface.
Und das war die Klasse, die dieses Interface implementiert.
Das ist der Unterschied. Normalerweise, wenn Sie nur mit
Klassen arbeiten, würde hier stehen, New Konto.
Sie würden ein neues Konto erzeugen. Hier erzeugen wir,
das mit Konto wird das aber nicht funktionieren,
weil Konto ein Interface ist.
Von Interfaces kann ich keine Exemplare erzeugen.
Deswegen sieht es hier so aus
Ja, wir schauen uns an, wie dieses Interface,
wie die Trennung veranschaulicht werden kann, im Klassendiagramm.
Wir haben also ein Kästchen für jede Klasse im Klassendiagramm
der UML und dieses Interface wird dadurch zu sehen,
dass es sich um ein Interface halten möchte, nicht eine Klasse,
dass wir diese Kennzeichnung mit diesen doppelten Spitzen klammern.
Eigentlich sind das die kleiner Zeichen und größer Zeichen,
dass wir diese Zeichen drin haben, die uns angeben,
diese Bezeichnung, Interface,
sie werden das später im Studium noch gelegentlich hören,
diese doppelten Spitzenklammern bezeichnet man als Stereotyp
Es gibt genauer an, es handelt sich um ein Interface
nicht um eine Klasse, diese Sorte Kasten.
Also das ist eine Klasse. Und das ist ein Interface,
weil das hier drinsteht. Die Schnittstelle wird von denen benutzt.
Hier gibt es einen gestrichelten Pfeil und diese Beziehung führt die Implementierung.
Das Schlüssel wird zwar ja vorhin im Implements,
da gibt es ein, das ist wieder eine ganz normale Klasse,
also hier gestrichelter Pfeil mit diesem offenen Dreieck.
Wir haben also jetzt wieder hier Kapselung und Information Highting
zu sehen in diesem Bild
Ich möchte einen weiteren Begriff anbringen. Spezifikation. Heute
zwei Begriffe des Tages. Spezifikation ist auch etwas,
was man als Informatiker relativ häufig in den Mund nimmt und hört,
was heißt denn Spezifikation?
Hat jemand einen Tipp, eine Vermutung, was könnte Spezifikation bedeuten?
Bitte? Was eine Methode genau machen soll,
damit bin ich noch nicht ganz so zufrieden.
Ich würde Ihnen gerne eine Hilfe geben, Das Englische to Specify,
das Verb spezifizieren. Was heißt denn das auf Deutsch?
Was sagen Sie? Ja, also ich wiederhole mal die Bedingungen,
die Aufgaben, was die Klasse machen muss.
Damit gehen Sie schon in eine gute Richtung,
dann gebe ich mir einfach die Übersetzung von to Specify wieder,
die ich sehe, so gelernt habe.
to Specify heißt
etwas genau bezeichnen
Und nichts mehr machen wir mit einer Spezifikation.
Wir machen eine genaue Beschreibung, eine genaue Bezeichnung.
Diesen Begriff Spezifikation benutzen die Informatiker typischerweise, wenn es darum geht,
die Aufgabenbeschreibung von Anforderungsbeschreibung vom Kunden, vom Auftraggeber aufzunehmen.
Das bezeichnet man häufig als Spezifikation. Dieser Begriff,
der taucht jetzt hier auf, also wir wollen genau beschreiben,
was etwas tun soll und das Interface können wir sehr gut dazu verwenden.
Und wir haben das Kontobeispiel, da haben wir zum Beispiel,
haben wir gesagt, wie diese wie dieses Interface aussehen soll,
dass es diese drei hochkomplizierten Methoden haben soll.
Und damit haben wir die Spezifikation,
die genaue Beschreibung des Verhaltens der Klasse schon gemacht.
Und umgekehrt können wir es auch noch sagen,
wir legen fest, was wir im Winter klasse machen können,
indem wir diese Spezifikation machen können.
Und was ist diese Spezifikation? Ist also eine Beschreibung der
gewünschten Eigenschaften, eben to Specify, genaue Beschreibung von etwas?
Und eine gute Beschreibung, eine gute Spezifikation sagt nur das
was und nicht das Wie
So, Spezifikation allgemein ist eben diese,
oder diese Trennung hier ist ganz wesentlich, dass wir,
das möchte man in allen modernen Programmiersprachen haben,
Trennung von Beschreibung und Interface.
Jetzt wieder zu dem Punkt Typ zurück,
der auf der allerersten Folie heute stand.
Die Klasse definiert den Typ, das Interface definiert Exel ebenfalls
einen Typ. Ja, und dieser Typ,
damit wollen wir diese Interface-Sache noch krönen und abrunden.
Wir brauchen diesen Typ,
so ähnlich wie man
den Datentyp hat
Betrachten wir erst mal, wie das mit diesem Typ aussieht.
Wir kennen den Begriff Datentyp, der Datentyp,
eben wie sie Real Inteture String von den einfachen Typen her kennen,
der definiert in Objekt nicht orientierten Programmiersprachen,
ist es eigentlich der einzige Typbegriff, den man hat.
Und das sind die Wertemenge. Inteture sind die ganzzahligen Zahlen
. Ja, und damit ist auch definiert,
das kann man verwenden für Variable und so weiter.
Und jeder Wert, jede Variable gehört genau zu einem solchen Typen.
Ich kann entweder Gleitkommazahlen haben oder ganze Zahlen.
Oder ich kann Zeichenketten haben oder irgendetwas anderes,
aber immer nur eine Sotte,
nichts anderes
Und diese Typinformationen kann man im Quelltext irgendwo nachlesen und auch
die Operationen, die müssen immer dazu passen.
Bei Intech-Shop kann ich zum Beispiel nicht dividieren. Ja,
da gibt es mal so eine Intech-Shot-Divid-Division mit Rest,
aber eine richtige Division kann ich da eben nicht machen.
Ja, so. In Objekte und Tittensprachen haben wir
diesen Typbegriff auch und das ist ein erweiterter Typbegriff.
Den möchte ich hier mit einführen. In der Übung wird
es unter anderem zu diesem erweiterten Typbegriff auch Fragen geben.
Das Übungsblatt können Sie sich ja schon anschauen. Ja,
und hier ist das Besondere, dieser Begriff wird in
der Wissenschaft auch als abstrakter Datentyp bezeichnet
Ist denn das nun wieder? Abstrakt ist ja irgendwie schwierig
, abstrakt ist immer schlecht greifbar. Hier ist gemeint,
man hat diese Operationen und die Wertemenge zusammengefasst.
Ja, Wertemengen mit den Zugriffsoperationen in einem Objekt gekapselt.
Das ist der Witz der Objektorientierung und deswegen hat man da gesagt,
okay, es ist was wie ein Typ,
aber irgendwie ein bisschen mehr und das ist ein abstrakter Datentyp.
Wir können das, es ist ein Synonym zu klasse
für uns. So einfach können wir das halten.
Und der Typ definiert jetzt nicht nur die Wertebereich,
sondern eben auch das Verhalten der Objekten an der Schnittstelle
Zur Folge ist es eben so, so wie vorher,
ein Wert immer nur einen Typ haben kann,
kann eine Klasse immer nur genau,
oder ein Objekt immer nur genau zu einer Klasse gehören.
Dass diese Sache ist unverrückbar. Fortsetzung davon,
es gibt auch noch etwas anderes,
es gibt nicht nur einen statischen, nicht nur der eine Sondertyp,
sondern wir müssen es noch aufteilen in statisch und dynamisch.
Statisch bedeutet, wir machen eine Zuweisung,
wir erzeugen eine Variable, das ist das,
was wir eben mit unserem Konto gemacht haben.
Wir erzeugen ein Konto und das ist es.
Da ändert sich nichts mehr dran,
deswegen bezeichnen wir das als statisch
Es gibt aber vielmals Fälle,
obwohl ich auf etwas verweise, was irgendwo anders ist.
Und der andere ist dafür verantwortlich, da etwas draus zu erzeugen
und gibt mir dann die Werte zurück oder den Inhalt.
Dann erzeuge ich die Sache nicht selbst. Ich verweise nur drauf.
Das ist die Referenz. Ja, das ist die Referenzvariable,
die wir gleich im nächsten haben. Also hier ist,
wenn ich hier zum Beispiel dieses Konto K,
da kann ich etwas einzahlen und so weiter, das ist fest.
Jetzt kommen wir zu dem dynamischen. Bei dem dynamischen ist
es so, dass ich zwar auch ein Objekt erzeuge,
Aber nicht direkt darauf zugreife.
Also hier, ich erzeuge K, ein Objekt,
ein Exemplar dieser Klasse. Aber wir haben erst zur Laufzeit,
können wir ermitteln, wer genau diese Implementierung ist.
Und wir können dann auch zur Laufzeit tatsächlich noch einmal etwas ändern.
Ich hatte eben gesagt, zwei Folien vorher,
ein Objekt gehört immer zu einem Typ
Durch diese Referenzvariabler können wir auch tatsächlich unterschiedliche Typen verarbeiten,
beziehungsweise auch zwischendurch da einen Wechselharfwai führen. Und das hat insbesondere die Aufgabe,
wenn wir eine konkrete Methode aufrufen,
wissen wir zu dem Zeitpunkt, wenn wir das Programm entwickeln,
noch gar nicht, was wird denn das für eine Klasse,
zu welcher Klasse wird dann die Methode gehören?
Es kann ja mehrere Klassen geben, einzahlen.
Bei der einen gibt es vielleicht verschiedene Konten,
da gibt es ein Gemeinschaftskonto und ein Sparkonto und ein Tagesgeldkonto.
Jeder hat eine Methode einzahlen, aber ich weiß noch gar nicht,
zu wem die gehört, weil ich den Kontotyp,
ich weiß nur, okay, auf dieses Konto einzahlen,
ich weiß auch nicht genau,
was für ein Typkonto es sein kann
Und das hängt deswegen, welches wir genau benutzen,
hängt bei diesen Referenzvariablen davon ab, wo es herkommt.
So, hier und ein neuer Begriff,
den ich Ihnen nicht ganz ersparen kann, das dynamische Binden.
Was heißt nun dynamisches Binden? Dynamisches Binden heißt,
dass wir, wir konzentrieren uns erstmal hier oben drauf,
dass wir, das, was wir eben schon hatten,
ich weiß zum Erzeugungszeitpunkt oder zum Zeitpunkt meines Programmschreibens gar nicht,
auf was werde ich denn zugreifen?
Also wir haben jetzt hier Winden,
ich habe wieder ein Weihnachtsbeispiel genommen, ja,
also wir haben hier einen Klienten,
der benutzt ein Interface
Und dieses Interface definiert etwas allgemeiner, wo wir es verschiedenen Implementierungen haben.
Wir haben jetzt hier also eine weihnachtliche Stimmungsbeleuchtung,
könnte wir erreichen, indem wir eine Lichterkette haben aus LEDs.
Und wir können eine Kerze nehmen. Bei beiden kann ich
das so machen, einschalten, ausschalten.
Das heißt, beim LED ist es ein bisschen mehr Schalten.
Bei der Kerze ist es ein bisschen was mehr mit Feuerzeug,
aber das Gemeinsame wäre, die können beginnen mit leuchten und können aufhören mit leuchten.
Das Interface definiert das Gemeinsame, nämlich diese beiden Methoden,
diese beiden Operationen und die Implementierung wäre jetzt hier,
wir kennen schon in diesem Pfeil diesen gestrichelten Pfeil hier zu den beiden, Das wäre hier zweimal.
Und hier oben, ich benutze das zum Beispiel,
um meine Weihnachtsfeier zu machen, ja,
bei Fanger an, steht dann, das soll hier,
der Quelltext dieser Methode fange an hier sein und der Methode fange an,
mache ich zum Beispiel Tee, beginne, leuchten.
Und ich weiß, wenn ich das hier schreibe, ich bin,
jemand von Ihnen ist der Autor, dieses Stücks Programm.
Er weiß noch gar nicht, was wird denn T für ein Objekt sein?
Wird ein T ein Objekt sein von hier oder von hier?
Das entscheidet sich erst danach, was ich denn für eins
erzeuge für Tee. Ja, ich weiß nur,
es ist vom Interface-Typ Stimmungsbeleuchtung und es wird diese Methode irgendwie können
Weil es ist eine Verpflichtung für den Programmierer gewesen.
Das Interface ist die Verpflichtung für den Programmierer. Das ist
ein relativ heftiges Thema, ein bisschen schwer zu verstehen.
Ich hoffe, mit dem Beispiel wird es Ihnen ein bisschen leichter.
Okay, ich will das Nächste vielleicht mal hier und überspringen.
Ganz wichtig, weil ich möchte zu den Tests noch kurz kommen.
Ich habe Friedschöne ein bisschen Zeit weggenommen. Es wird jetzt
ein bisschen knapp. Zehn Minuten. Naja, mal schauen
mal. Okay, wir wollen diese Schnittstelle benutzen.
Ich hatte ganz Anfang gesprochen bei der Motivation davon,
dass man sich darauf verlassen können muss.
Das ist das, was wir durch Tests absichern wollen.
Und durch Tests absichern heißt auch, Dass wir zusichern wollen,
das wird wirklich so gemacht. Und dafür gibt es diese Typ-Too-Sicherung,
das wollen wir auf dem nächsten Folie ein bisschen genauer angucken.
Zusicherung bedeutet, ich kann mich darauf verlassen, dass es so gemacht wird.
In dem Fall kann ich hier, wenn es eine Referenz ist, dann wird das so passieren. Okay,
so, wir sind, gut, ach, im Klassendiakram,
das hat man aber eben schon mal gesehen,
gibt es noch die Folie, die noch ein bisschen genauer erläutert,
was ich vorher sagte, hier oben,
das war also dieses Stereotyp mit den Spitzenklammern,
das heißt Stereotyp,
für Interface, dass wir das eine Klasse haben.
Hier gibt es zwei Linien, dazwischen steht nichts,
der Abstand ist ja ein bisschen dünn geraten.
Zwischen den zwei Linien wird normalerweise die Attribute gehören beim Klassendiagramm
und dieser gestrichelte Pfeil steht für die Realisierung.
Okay, Zusammenfassung, wir trennen Schnittstelle und Implementierung.
Es gibt unterschiedliche Implementierungen, die möglich sind von dem Interface.
Das ist eine benannte Schnittstelle, davon können wir keine Objekte erzeugen.
Die hat erstmal die keine Methodenrümpfe haben und dann gibt es
noch diesen statischen und dynamischen Typ.
Bitte schauen Sie sich das nochmal für die Übung an
Kurz zum Testen. Testen ist eigentlich, müsste eigentlich viel
mehr Zeit haben, aber die haben wir heute nicht.
Deswegen wenigstens so kurz um die Bedeutung klarzumachen. Testen ist etwas,
was sehr aufwendig ist, was dafür zuständig ist,
dafür zu sorgen, dass wir keine Fehler haben.
Testen ist typischerweise dreimal so aufwendig wie Implementierung. Das heißt,
sie können rechnen, wenn von ihnen, von 30 Leuten von uns,
45 Leute von uns irgendwo arbeiten in der Softwareentwicklung,
dann werden 30 davon als Tester arbeiten und nur zehn werden Softwareentwickler sein.
Einfach nur vom Aufwand her. Das Testen hat eine große Bedeutung,
hier ist so ein Desaster gezeigt,
was man typischerweise gerne anführt als Beispiel,
mein Mann doch richtig getestet hätte, dann wäre dieses Millionendesaster nicht passiert.
In der Tat ist es ganz schwierig, nachzuweisen,
dass Software fehlerfrei ist. Insbesondere den oben, den Herrn Deixtra,
einen Erfinder im berühmten Informatiker und Erfinder von mehreren Programmiersprachen,
wollen wir hier mal nehmen, ja,
man kann eigentlich durch Testen nicht zeigen, dass ein Programm fehlerfrei ist.
Ich kann nur versuchen, Fehler zu finden,
ich kann aber nicht nachweisen, dass ich keine Fehler habe.
Das macht das Testen so schwierig. Trotzdem ist es das Einzige,
was wir haben und Wir müssen deswegen überlegen, was ist korrekt?
Korrekt heißt immer, die Software erfüllt die Spezifikation,
diesen Begriff Spezifikation, hat man ja eben nur ein Formalnachweis,
was hier in der zweiten, im zweiten Punkt steht.
Formal im Sinne von, wie ein Beweis, das existiert nur,
wenn wir tatsächlich auch die Aufgabenstellung formalisiert beschreiben können.
Meistens haben wir die Aufgabenstellung aber nur in natürlicher Sprache und das macht das Ganze schwierig.
Für umfangreiche Programme gibt es diese formalen Beweise eigentlich nicht.
Das gibt es nur für Programme, wo Geld quasi keine Rolle spielt,
weil das Risiko so hoch ist, dass es sich trotzdem lohnt.
Also solche Weltraumsachen sind ein Beispiel, außerdem,
wo Geld keine Rolle spielt,
ist meistens noch
die Rüstung
Was gibt es für Testen? Wir wollen typischerweise testen.
Gibt es denn diese Eigenschaften? Aber das reicht nicht.
Wir brauchen noch mehr. Wir müssen auch nachweisen,
dass es bestimmte andere Eigenschaften auch vor einigen bestimmten Sachen auch geschützt wird.
Und das ist der Begriff, also das positive Testen,
Positivtest, das ist das, was wir erwarten.
Die erwarteten gültigen Eingabewerte, das ist das Positivtesten.
Wir wollen also zum Beispiel, wir wollen aufs Konto
einzahlen und erwarten, der Betrag ist hinterher höher.
Das wäre ein Positivtest,
wenn wir das prüfen
Ein Negativtest wäre, wenn wir aus dem Konto etwas auszahlen wollen,
und das Konto war vorher schon leer,
dann sollte bitte eine Fehlermeldung kommen, sagen, Auszahlung ist nicht möglich.
Das wäre ein Negativtest. Ein Negativtest ist etwas, was
Fehlermeldungen provoziert. Und positive Tests sind wichtig für uns,
weil wir wissen dann, die Software tut das,
was wir von ihr erwarten, sie ist korrekt.
Und der nächste Punkt ist Vertrauen in Robustheit.
Dazu brauchen wir aber die Negativtests. Meistens fallen den Entwicklern,
fällt es viel schwerer, einen Negativtest zu schreiben als einen Positivtest.
So, Ein wichtiges Prinzip, was entwickelt worden ist,
weil Testen immer zu kurz kommen, die Leute entwickeln,
entwickeln, entwickeln und lassen ganz viel an Tests weg.
Deswegen gibt es die sogenannte testgetriebene Entwicklung,
Entwicklung, bei der Informatik ist das Englisch sehr populär,
Neudeutsch heißt das also, Testdriven oder Test First to Principle.
Die Reihenfolge ist der, dass man zunächst erst mal überlegt,
was ist die Schnittstelle, was ist die Spezifikation?
Das sollte man unbedingt auch immer, man sollte vorher sich ja immer überlegen,
was muss ich hier überhaupt tun, was sind die Ziele?
So, diese Spezifikation schreibe ich zunächst auf.
Und dann entwickle ich aus dieser Spezifikation,
wir hatten die ja eben im Zusammenhang mit dem Interface für diese Spezifikation,
für dieses Interface entwickle ich eine Testklasse.
Eine Testklasse, die positiven Tests abdeckt, das wäre das fehlerfreie Verhalten,
die Negativtests abdeckt durch schulbekannte Fehlstege oder einfach nur für das nächste Stück Code.
Und dann erst als drittes,
also bitteschön nach dem Test,
entwickle ich erst
den Code
Zunächst wird der Nitrisch den Käst nicht erfüllen,
weil erstmal ist nichts da, aber dann,
wir entwickeln den so lange,
bis er das erfüllt und dann sollte man als Viertes,
damit ist eigentlich die Arbeit erledigt, viele Leute vergessen dann den vierten Punkt,
der vierte Punkt besteht darin, dass ich den,
nachdem ich ihn mehrfach geändert habe, tatsächlich aufräume,
also vereinfache, besser lesbar mache, wirklich Kommentaren mache,
ich bin da noch nicht fertig,
erster Punkt 4 noch und das mache ich so lange.
Es gibt das als sehr etablierte Technik, viele Leute arbeiten damit,
sind sehr erfolgreich, aber noch viel mehr Leute,
um das ganz ehrlich zu sagen,
haben da ihre Vorbehalte,
machen das einfach nicht
Ja, und zwar die wesentlichen Argumente habe ich mal hier unten aufgeschrieben.
Ein Argument ist, viel zu aufwendig. Ja,
das tatsächlich, das ist ein überzeugendes Argument,
wenn sie so weit angucken, gucken, dann haben sie,
dann haben sie so viel für Testklassen und nur so viel ist die wirkliche Klasse.
Und dieses Verhältnis klingt doch irgendwie nicht gesund,
aber tatsächlich ist es so, wenn man diese Testklassen nicht macht,
dann hat man so viel versteckte Fehler noch drin,
vor allen Dingen welche,
die sich erst spät zeigen und man kann sich nicht drauf verlassen,
dass der Gesamtaufwand wird am Ende viel,
viel höher
Um das zu machen, braucht man doch tatsächlich viel Disziplin.
Und das ist eigentlich der springende Punkt. Man braucht an
vielen Punkten Disziplin tatsächlich nicht zu beginnen mit der Implementierung,
nicht mit drittens zu Beginn, bevor man nicht zweitens die Testklasse hat.
Ja, und wir haben aber eine Belohnung dadurch,
dass wir hier viel mehr Vertrauen haben.
Wie sieht sowas aus? Wir schreiben zu jeder Klasse eine Testklasse.
Das werden wir hier auch noch in SE1 machen.
Diese Testklasse entliefert das, was die Spezifikation beschreibt.
Das haben wir hier unten stehen. Ja,
also die Testklasse macht die Tests entsprechender Spezifikation und dann auch noch die Sachen soweit,
dass nicht nur jedem, nicht jeder Testfall einmal da ist,
sondern dass auch noch jede Operation mindestens einmal aufgerufen wird.
Dadurch, dass man das so geschrieben hat,
kann man sehr leicht einen Test immer wieder durchführen.
Wir kommen gleich zu Tools. Und wenn man das immer
wieder macht, da gibt es noch einen Namen dafür,
der ihn gelegentlich über den Weg laufen wird.
Man nennt das Regressionstests. Also man macht den Test,
der nun automatisiert, immer wieder, wieder, wieder,
bis am Ende man nach und nach die Fehler
alle behoben hat und keine mehr auftreten
Es gibt dazu Werkzeuge und das macht das Ganze gleich den Aufwand,
den von dem ich eben gesprochen habe, wieder ein bisschen aus.
Und das Werkzeug, wo du dir sie hier vertraut machen wollen,
ist Jay Unit. Das ist ein Java-Werkzeug für Unitests.
Das Mutter von welchen der Vorvätern geschrieben,
also hier Kent Beck und Erich Gamma sind hier genannt.
Und damit kann man diese Testklassen sehr bequem erschreiben und kann das
sehr bequem ausführen. Jay Unit.org, das ist frei verfügbar,
ist wirklich ein sehr mächtiges Werkzeug, was den Nutzen betrifft.
Ansonsten ist es relativ schlank. Wie arbeite ich damit?
Ich muss zunächst eine solche Testklasse erstellen
Das ist der erste Punkt, den ich machen muss.
Ja, also die Testklasse muss dieses Format haben und in
der Testklasse müssen Testfälle drin sein. Also die Test-Ops,
das war zu schnell, die Testklasse muss Testfälle zu haben
und die müssen dazu passen, mit der zu testenden Klasse.
Wie sieht so ein Testfall aus? Testfall heißt,
ich habe die Eingabedaten und prüfe, ob die Ausgabedaten dem erfüllen.
Um so einen Testfall zu schreiben, gibt es ein neues Schlüsselwort,
was wir bisher vielleicht noch nicht kennengelernt haben.
Das heißt Asserd. Dieses Asserd steht hier irgendwo, genau
, hier oben, ja. Und diese Asserd-Methoden beschreibt,
was wir erwarten. Ja, also wir haben hier,
wir haben hier, das erste kennen wir schon,
nein, ich war zur falschen Taste.
Wir haben hier erzeugt eine Methode, von da erzeugen wir eine Instanz.
Also nein, das war das, das war das Interface.
Und für das Interface erzeugen wir passend zu dem Interface
eine Instanz der Klasse Kontosimple.
Und hier passiert wirklich etwas. Wir zahlen auf die Klasse
K, auf das Objekt Karl zahlen wir etwas ein
Also diese Methode auf. Und jetzt kommt die Testmethode.
Also das war der Aufrufparameter. Und jetzt gucken wir,
ob wirklich eine 100 rausgekommen ist, wenn wir Gibsalto aufrufen.
Und wenn nicht, würde uns dieser Text hier vorne angezeigt,
dass hier vorne ist also schon die Anzeige,
die dann im Logfile oder im Terminal passiert, ja,
in der Konsole uns gezeigt wird.
Damit also ganz leicht Testfall besteht aus, ich erzeuge etwas,
die Umgebung, in dem Fall nur das Objekt erzeugen.
Ich rufe eine Methode auf, die ich testen will.
Und dann Prüfe ich, ob das Ergebnis nachprüfbar ein Gipsalto,
ob das wirklich erfüllt ist.
Damit kommen wir schon fast in die Nähe des Schluss.
Wir haben, wir wollen diese Testfälle auch ausführen. Das machen wir,
indem wir das jeweils ausführen, so ein Exemplar erzeugen.
Und wenn die alle fehlerfrei sind, diese Tests,
dann haben wir so einen schönen grünen Balken, den sieht man dann als kleine Belohnung.
Und wenn das nicht passiert, typischerweise haben wir eine Reihe von Testfällen,
irgendeiner ist fehlgeschlagen, haben wir das Ganze in Rot.
Die vorgegeben. Ein bisschen brauche ich noch Strom. Ja,
die vorgegebenen Ja Unit liefert einiges mit, was uns die Arbeit erleichtert. Und das sind dieses Assert, hatten wir schon gerade gesehen. Ja,
also wir haben hier Third Equals,
das war eben schon drin, ich kann erwarten,
ist etwas gleich oder Assert same sind die Objekte tatsächlich identisch,
nicht nur die Werte gleich oder Assert true issort falls,
dass wir einen Bullyen-Wert haben,
einen Wahrheitswert war oder falsch oder dann hier null oder not null,
ob etwas erzeugt ist,
ob eine Referenz existiert oder ob sie nicht existiert und diese Sachen kann man dann relativ leicht prüfen und dann gibt es,
so wie beim Auto in Testsiegel,
kann man hier sagen,
wunderbar,
wir haben es geschafft
Ja, Zusammenfassung, dieses Testen hat etwas mit Qualität zu tun,
aber es leistet nicht den Nachweis, dass wir keine Fehler mehr haben.
Es hilft uns nur, Fehler zu finden und
dazu muss man eine schlaue, clevere Testfälle haben.
Das Ganze ist anspruchsvoll im Sinne von kreativ bezüglich gute Testfälle und Disziplin erfordernd.
Das Test-Driven hilft uns dabei, es ist eigentlich nur ein
Handwerk und ich sollte zu jeder Sache eine Testklasse schreiben.
Das sollten wir immer versuchen und es gibt natürlich dieses nette Werkzeug.
Jay Unit, was das vereinfacht
Damit übergebe ich einen Fred,
der uns noch das Ganze noch etwas schöner zeigen wird.
Darf ich dich hier umstöpsen?
Vielen Dank
Hattest du zu
Musik Ah, hallo? Ja. Ja,
ich muss ja mal irgendwas sagen am Anfang, damit man
mich einpegeln kann und mir fallen immer nur Prinzzahlen ein.
Zwei, drei, oh Gott, Passwort eingeben,
während man Prinzzahlen aufsagt, das funktioniert nicht.
So. Und wie ist das, wie ist der Sound?
Ist okay? Okay. Also ich habe ein bisschen was
zum Testen und ich habe ein bisschen was zu den Schnittstellen.
Und ich dachte, ich fange mal mit dem Testen an,
weil ich eigentlich relativ klar,
ich mache hier zehn Minuten was über Testen und dann habe
ich den Rest der Zeit flexibel,
kann ich den dann füllen mit dem anderen Thema
So. Und das Testen knüpft ja auch
ganz gut an an das, was wir gerade haben.
Es gibt so ein bisschen Feedback, oder?
Irgendwie piept das so ein bisschen, habe ich den Eindruck.
Muss ich hier irgendwas, soll ich es leiser machen oder so?
Ja, ich glaube, es geht schneller,
wenn du das machst. La, la, la,
la. Ist es besser? Ich weiß nicht,
und ich drücke auch nochmal zusätzlich, glaube ich,
hier rauf. Das vergesse ich nämlich sonst auch immer.
Okay. Also, ich habe mal Folgendes gemacht,
ich habe unseren Paar so nochmal so ein ganz kleines bisschen beschnitten,
den wir letzte Woche gebaut hatten.
Nochmal ganz kurz als Erinnerung, wir hatten ja,
um Rekursion zu verstehen,
hatten wir uns ja so ein Paar so gebaut,
mit dem man so schön
mathematische Formeln parsen kann
Wir waren also so weit gekommen, dass das irgendwie für Ziffern funktioniert hat.
Dann haben wir Produkte gepasst, dann haben wir Summen gepasst und das war's.
Und später haben wir dann noch geklammerte Ausdrücke eingebaut.
Und wie haben wir das eigentlich immer getestet? Wir haben
nur das Wort Testen, passt eigentlich ganz gut dazu.
Wir haben uns ja eigentlich hier recht manuell,
entweder das in der Konsole erzeugt,
ich könnte mir das aber auch genauso gut hier erzeugen,
ich erzeuge mir mal kurz ein paar, sondern nenne den mal P.
Und dann würde ich jetzt hier von Hamp zum Beispiel eingeben,
p.pas, sagen wir mal zweimal drei.
Und ich würde jetzt ja erwarten, ich will ja immer,
dass ihr euch das vorher klar macht,
dass da
sechs Ball rauskommt
Genau, tut es auch. Und ich könnte
dasselbe Spiel morgen machen und übermachen. Und jedes Mal,
wenn ich was in die Pause ändere,
könnte ich immer wieder manuell diese Tests durchlaufen lassen.
Und viel schöner wäre es, ja, wenn das automatisch und reproduzierbar passiert.
Und tatsächlich ist das Bluedray auch schon ganz nett in den Share Unit eingebaut.
Nee, andersrum. J. Unit ist im Bluedrainer gebraucht.
Wenn ich hier als eine neue Klasse anklicke,
kann ich sagen, ich will einen Unit-Test schreiben.
Nennen wir das doch mal Phaser-Test. Und da wird schon
ziemlich viel Grundgerüst für euch erzeugt. Ihr seht auch,
das sieht ganz anders aus, das ist so komisch grün,
dann steht hier noch, das ist tatsächlich ein Unit-Test,
das ist nicht nur eine ganz normale Klasse.
Schauen wir mal rein
Was da alles erzeugt wird. Erstmal zwei wahnsinnig hilfreiche Leerzahlen,
die lösche ich mal, weiß ich nicht, warum die erzeugt werden.
Okay, dann gibt es hier noch so zwei Methoden,
die könnt ihr eigentlich immer gleich am Anfang löschen,
wenn ihr eine Testklasse schreibt, zumindest am Anfang.
Das braucht ihr nur, wenn ihr kompliziertere Testfälle habt,
die irgendwelche Datenbankverbindungen öffnen und schließen müssen oder irgendwas.
Das ist im Kontext von SR1 überhaupt nicht relevant,
damit es euch nicht verwirrt, lösche ich es mal sofort.
Okay, jetzt hast du eine Testklasse. Normalerweise immer ein Exemplar
der Klasse, die wir testen wollen, als Exemplarvariabler.
Also sagen wir mal zum Beispiel,
ich will einen Pause testen,
nenne den Pause
Und im Konstruktor erzeugen wir uns mal ein Exemplar von Pause.
Ist gleich New Pause. Das ist jetzt relativ einfach,
weil man auf dem Paser jetzt nicht noch großartig verändernde Operationen aufrufen kann.
Gut. Und jetzt schreibe ich mal meine erste Testmethode.
Nennen wir die mal vielleicht alle einstelligen Zahlen.
Ich möchte also sicher gehen, vielleicht einmal kurz vorher manuell,
ist mein Exemplar noch da.
Das ist noch da. Das zum Beispiel
, das funktioniert
Das funktioniert oder dass das funktioniert. Das will ich also sicherstellen.
Und dazu benutze ich jetzt diese Shirt-Equels. Ich sage also,
und auf diese selbstgebaute Fehlermenge verzichte ich jetzt mal,
ich erwarte, dass der Wert null rauskommt,
wenn ich auf dem Parser die Methode Pars aufrufe und den String null übergebe.
Ja, und wenn das jetzt mal ein bisschen schneller geht,
lasse ich einmal ganz kurz den konkreten Werte hier weg,
copy paste mir das einmal ganz kurz,
man kann das hier reinfüllen.
Und die erste Intuition ist jetzt immer so,
ist das nicht Zeitverschwendung, das zu testen?
Wir wissen doch, dass es funktioniert.
Ich werde euch gleich mal zeigen, dass es gar nicht
so sinnlos ist
Schleif vorbei. Das sind eine Schleife packen, ja,
könnte man wahrscheinlich machen, da muss man sich die Frage stellen,
wie generiere ich diesen String?
Ich wollte jetzt möglichst blöd machen,
damit man sich nicht noch über diese Schleifengedanken machen muss.
Okay, das ist unsere erste Testmethode.
Und wie führe ich denn jetzt so einen Test aus?
Das ist in Bluejoy relativ einfach. Ich klicke einfach auf
die Testklasse und habe jetzt die Möglichkeit, entweder zu sagen,
ich will alle Testfälle ausführen oder nur diesen einen.
Das macht jetzt in unserer Fall natürlich keinen Unterschied.
Normalerweise hätte man ganz viele Testfälle. Wir schreiben gleich noch
einen mehr. Ich sag mal, ich will alles testen.
Und dann kriege ich hier so ein Feedback.
Und rein optisch sieht man jetzt sofort,
es scheint irgendwie alles in Ordnung
zu sein
Also dieser Testfall ist durchgelaufen, wenn es jetzt noch
mehrere Tests wieder gäbe, würden die hier aufgelistet werden.
Bei jedem Testfall würde entweder so ein Häkchen oder so ein rotes Kreuz erscheinen.
Und wenn überall Häkchen erschienen sind, dann ist dieser Balken grün.
Das ist dieser Balken, den Matthias auf der Vorjahr angesprochen hatte.
So, was soll denn jetzt dieser Test?
Also letzte Woche kamen ja schon Studenten zu mir und meinten,
hey, in der Implementation von dem Parser,
ich habe jetzt zu viele Fenster auf.
In der Implementation von dem Parser,
da hast du ja hier alleine schon zehn Zeilen Code,
nur um zu erkennen, ob es eine Ziffer ist oder nicht.
Das ist ja irgendwie ganz schön umständlich.
Das geht auch bestimmt auch einfacher
, oder
Und oft machen Programmieranfänger dann folgendes. Ich schaffe es erst
mal darüber, dann lösche ich es danach. Die sagen dann,
naja, ich kann ja einfach testen,
ob das Zeichen zwischen 0 und 9 liegt,
das kann ich doch mit dem IF irgendwie sicherlich sinnvoll testen.
Also ich teste, ob das Zeichen größer ist als 0 und ob es Zeichen,
hups, kleiner ist als 9 zum Beispiel.
Diesen Code habe ich schon so oft gesehen,
ich erlaube mir jetzt einfach mal, den auch hinzuschreiben.
Auch wenn ihr jetzt vielleicht schon den Fehler seht. Okay,
wenn das also so ist, dann kann ich mir diese Zahlen,
diesen beiden Zahlen hier einfach mal rausschneiden.
Und im anderen Fall, ups, im anderen Fall muss Error-Handling betreiben.
Damit haben wir uns ja noch gar nicht beschäftigt.
So, dann nehme ich das jetzt mal weg.
Und juhu, viel weniger Code. Toll. Und
ihr könnt euch wahrscheinlich auch vorstellen, dass derjenige,
der diese Änderung gemacht hat, ist vielleicht jemand anders.
Man weiß auch nicht genau, wann er das gemacht hat und so.
Na gut, ich habe das jetzt mal geändert.
Alles ist übersetzbar. Dann führe ich doch mal meine Tests aus.
Oh. Weiben ist rot. Das ist ja interessant.
Das heißt, ich kriege relativ schnell das Feedback,
wenn jemand was ändert und ich regelmäßig meine Tests durchlaufen lasse,
dass da jemand was kaputt gemacht hat
Das ist doch schon mal nicht schlecht. Kriegen wir dann noch
ein bisschen mehr Informationen. Also wenn wir hier raufklicken,
steht hier, es wurde einen neuen erwartet, aber es kamen null raus.
Okay. Und kann ich mir auch irgendwie anzeigen lassen,
wo das genau war? Man sieht hier so einen kleinen Stack-Trace,
den werdet ihr jetzt wahrscheinlich noch nicht verstehen können.
Und wenn ich jetzt hier auf Show-Source gehe,
dann sagt er mir, dass diese Zeile fehlgeschlagen ist.
Und das ist eigentlich hier wirklich ganz nett.
Also es wurde erwartet, dass ein neun rauskommt.
Es ist aber tatsächlich null rausgekommen. Das ist ja
irgendwie nicht so schön. Dann schauen wir doch mal vielleicht
in unserem Parsa, woran das liegen könnte.
Sieht jemand
den Fehler
Einmal Handruf, wer den Fehler, seht ihr ich,
keine Angst, ich nehme niemanden dran, nur dass ich mal so ein Stimmungsbild sehe.
Okay, und jetzt melden sich bitte nur die Leute,
die mir sagen wollen, was der Fehler ist. Weil da hinten vielleicht? Richtig,
ich habe also an dieser Stelle gefragt,
ist neun kleiner als neun, ist es nicht,
ja, dann hätte ich jetzt hier normalerweise irgendwie Fehlerbehandlung gemacht,
habe ich hier aber nicht gemacht, das heißt,
wir laufen hier einfach durch, geben das zurück,
was eine Variable Result drinsteht und ich habe sie am Anfang mit null initialisiert.
Deswegen kam da null zurück. Also hier muss kleiner
gleich stehen. Sieht jemand noch einen Fehler?
Richtig, das ist ein sehr interessanter Fehler. Der wurde jetzt dadurch verdeckt,
dass ich hier zufällig mit dem für null korrekten Ergebnis initialisiert habe.
Also wenn ich den Fehler jetzt mal nicht behebe,
man muss noch ein bisschen planen,
dass ihr es die ganze Zeit seht,
ich lasse den Fehler jetzt mal drinnen,
dann wird mein Test das nicht aufdecken.
Das ist jetzt nicht die Schuld des Tests, sondern das ist ziemlich blöder Zufall.
Also wenn ich jetzt zum Beispiel stattdessen gesagt hätte,
ich weiß am Anfang noch nicht genau,
was das Result ist, ich nehme mal minus eins,
dann hätte der Test das aufgedeckt, einmal übersetzen.
Dann sagt er mir wahrscheinlich, hey,
ich habe null erwartet, aber tatsächlich kam minus eins zurück.
Okay, dann korrigieren wir das auch mal eben
Und ich selber war tatsächlich schon mal in der Situation,
ich habe irgendwann mal Code optimiert, es ging um Kerrel,
da ging es irgendwie um das Negieren von Bedingungen.
Und ich habe durch diese Optimierung, habe ich das so zerschossen,
dass ich in einem IF keine negierten Bedingungen mehr prüfen konnte.
Das habe ich aber erst einen Monat später gemerkt,
als ich das irgendwo eingesetzt habe. Das war ganz schön
peinlich. Dann kriegte ich auf einmal E-Mails von Studenten,
hey Fred, deine Musterlösung, die du mir gesteht hast,
geht ja gar nicht.
Und dann, ich bekam diese E-Mail ungefähr 50 Mal.
Das ist nicht
so angenehm
Also das finde ich wirklich nett zu sagen, okay,
ich habe schon ein bisschen ein Stück Code,
ich bin ziemlich sicher, dass das funktioniert.
Ich schreibe jetzt mal Testfälle dafür, damit ich für die Zukunft mich absichere,
dass nicht irgendwann durch Änderung am Code auf einmal irgendwas nicht mehr funktioniert.
Und das Code geändert wird, passiert ziemlich häufig,
kann man sich wahrscheinlich vorstellen. Okay, das ist also
die eine Herangehensweise, die ich persönlich schon erlebt habe, das lohnt sich.
Und die andere Herangehensweise an Testen ist tatsächlich dieses Test-First,
dass ich sage, der Paser im momentanen Zustand,
so wie ich ihn jetzt zurückgebaut habe,
kann zum Beispiel noch keine Geklammerten ausdrücken.
Anstatt dass ich das jetzt zuerst implementiere, die wir Klamotten ausdrücke,
könnte ich jetzt auch erstmal den entsprechenden Testwahl dafür schreiben.
Ich könnte also zum Beispiel hier
einen Testwahl schreiben
Und wie nennen wir den mal? Geklammerte Ausdrücke oder so
. So. Und da könnte ich zum Beispiel sagen,
ich erwarte, wenn ich zum Beispiel sage paser.pas,
sagen wir mal 1 plus 2, mal 3 plus 4,
3 mal 7 bis 21, ne?
Da erwarte ich, dass da 21 rauskommt.
Dieser Test ist noch nicht mal übersetzbar. Doch,
Entschuldigung. Doch, aber der wird fehlschlagen. Nee,
ich hatte recht, dass er nicht übersetzt wird,
das will ich in der Kammer zu vergessen haben.
Aber er wird fehlschlagen jetzt
Ja, seht ihr jetzt hier.
Und jetzt könnt ihr euch zum Beispiel hingehen und sagen,
oh, ich habe ja schon Feierabend, kein Problem,
ich habe ja einen Testfall geschrieben, der ziemlich klar spezifiziert,
vielleicht noch mit einem Kommentar darüber oder so.
Was ich erwarte, was jetzt in dem Paser eingebaut werden muss.
Dann könnte jetzt jemand anders vielleicht in seiner Nachtschicht
das implementieren und dann komme ich am nächsten Morgen,
führe diesen Test aus und freue mich dann total, dass es funktioniert.
Tests als eine Art ausführbare Spezifikation, das finde ich auch ganz,
ganz interessant. Okay, ich glaube zum Thema Negativtests,
das lasse ich jetzt aus Zeitmangel mal lieber,
ich will jetzt lieber zu dem Thema Interfaces kommen,
weil das für die Übung auch so relevant ist.
Gibt es sonst noch Fragen zum Thema Testen,
was ich jetzt noch schnell beantworten könnte
Ja. Man wird das im Zweck für stark schilden,
sagt er. Mit einer syntaktisch falschen Sache.
Also irgendwie eine Klammer vergessen oder ein Ausrufezeichen. Ja,
das Blöde ist ja, dass wir hier noch nirgends gesagt haben,
was soll eigentlich passieren, wenn da Unsinn in dem String drinsteht?
Das wäre, das würde jetzt in Richtung negativ testen gehen.
Hey, dann löse ich in der Exception aus und die
Testklasse prüft dann, dass diese Exception auch tatsächlich ausgelöst wird.
Aber das würde jetzt den Rahmen sprengen.
Okay. Frage, ja. Das sind zwei Mikros.
Die gemeinsame Assus
Ah, das ist eine sehr gute Frage.
Benutzen alle Testmethoden denselben Parser? Das können wir uns einmal
klar machen. Ich kann nicht mal auf die Konsole schreiben,
sagen wir mal, euer Parser initialisiert oder so.
Ja, eigentlich ist es ja noch anders.
Es wird ja sogar... Eigentlich ist es ja sowieso,
dass wir die Testklasse initialisiert wird.
Und wir wollen mal schauen, wie oft das passiert.
Ich weiß nicht, ob das schon einen guten Hinweis liefert
Wo ist meine Konsole? Hier. Das ist eine Besonderheit von J. Unit,
die vielen Programmieranfängern nicht klar ist und die waren mir auch jahrelang nicht klar.
Es ist also so, dass J. Unit sich diese Testklasse
anguckt und feststellt, es gibt hier zwei Testfälle.
Dann erzeuge ich mir zwei Exemplare dieser Testklasse und auf dem
einen Exemplar rufe ich nur den ersten Testfall auf und auf
dem zweiten Exemplar rufe ich nur den zweiten Testfall auf.
Ihr müsst also nie Angst haben,
dass diese Testfälle sich irgendwie ins Gehege kommen.
Also wenn es zum Beispiel ein Konto testet und hier zahle ich
was ein und in einem anderen Testfall zahle ich was aus,
das sind dann verschiedene Konten, auf denen da gearbeitet wird.
Also da braucht ihr keine Angst haben
Okay, ich will vielleicht noch einen Test schreiben, einfach,
dass ihr mal so ein paar schöne Testfälle gesehen habt,
weil als Programmieranfänger weiß man oft gar nicht,
was soll ich denn jetzt heißen.
Man könnte zum Beispiel testen, dass die Punktvorstrichrechnung funktioniert.
Vielleicht wird der Paser irgendwann mal so umgebaut,
dass das irgendwie zerschossen wird und ich will das mitkriegen.
Also ich könnte zum Beispiel sagen, hups, wenn ich
dem Paser sage, Pase doch mal zweimal 3 plus 4 mal 5.
Das wird ja nur ein sinnvolles Ergebnis rauskommen.
Was wäre das denn? 2 mal 3 ist 6,
26. Also wenn das irgendwie ganz stumpf,
einfach nur von links nach rechts durchgerechnet würde oder von rechts,
von links nach rechts oder von rechts nach links,
dann würde da sicherlich ein anderes Ergebnis rauskommen.
Ich hoffe mal, es funktioniert,
ich teste das
noch einmal
Genau, das ist ja klar,
dass das noch nicht funktioniert, aber das funktioniert.
Gut. Das Thema Testen wird im Laufe des SE-Zyklus immer wichtiger werden.
Also in SE2 werden wir tatsächlich von euch verlangen,
dass ihr eigene Testklassen schreibt. Am Ende von SE1 werden
wir euch Testklassenvorsätze geben, die ihr gegen implementieren müsst.
Das ist quasi so ein bisschen wie dieser Testfall.
Wir liefern euch Testfälle, die noch nicht funktionieren.
Und ihr müsst dann die eigentliche Klasse so implementieren, dass diese Testfälle durchlaufen.
Okay. Und wenn ihr mal so ein Praktikum bei uns
macht, da sind die Tests noch zentraler.
Gut. Dann zum Thema Interfaces
Wo wollte ich denn da anfangen? Hier wollte ich anfangen. Also,
ich habe Interfaces eigentlich in zwei verschiedenen Kategorien oder Denkweisen kennengelernt.
Einmal das, was sehr stark auf den Folien von Matthias
heute war, das Interface spezifiziert ein Verhalten.
Da steht also irgendwie ganz exakt drin, was Klassen,
wie Klassen etwas, wie nicht,
was Klassen realisieren müssen und wie sie es realisieren müssen,
ist ihnen dann selbst überlassen.
Aber das erwartete Verhalten oder wie sich das verhalten soll,
das steht da ziemlich speziell drin.
Also wir haben hier mal so ein Beispiel,
Interfacezahlen sagt, das kommt in den Übungen vor
Wo also tatsächlich relativ ausführlich kommentiert ist, was man von so einem Zahlensack erwartet.
Das kann man sich einfach mal durchlesen. Mein Zahlensack ist ein Datentyp,
welcher eine gewisse Anzahl an unterschiedlichen zufälligen Zahlen liefert.
Die Zahlen liegen dabei immer zwischen null und größer minus eins. Das heißt,
ein Zahnsack der Größe zehn,
die verzugszuverzahlen aus der Menge 0 bis 9. Dieses Problem hatte ich tatsächlich selber auch mal,
da habe ich mal einen kleinen Tetris-Klon geschrieben und ich weiß nicht,
ob ihr das wisst,
wenn ihr einfach nur naiv genug Zufallszahlengenerator in Tetris verwendet,
dann kriegt ihr nie die Steine,
die ihr braucht und dann kommen über die blödesten Steine fünfmal hintereinander und so,
das macht irgendwie keinen Spaß.
Also ist es mittlerweile ein Standardtetris und so,
es wird einen Sack erzeugt mit allen Steinen,
die es gibt
Also sieben Stück, dann werden die euch nach und nach rausgeliefert
in zufälliger Reihenfolge. Und wenn er sagt, leer ist,
wird er wieder frisch aufgefüllt mit sieben, neunem Stein, aber immer verschiedene.
Das heißt also auch zum Beispiel der Abstand zwischen zwei langen Steinen kann,
glaube ich, höchstens zwölf andere Steine zwischendurch sein.
Dann ist das Spiel nicht ganz so frustrierend.
Das kann man also relativ gut spezifizieren auf Deutsch,
also ich oder auf Englisch, in Java ist es eher schwierig.
Aber die Frage, wie ich das jetzt baue,
dass ich so einen Zahlensack habe,
der so langsam schrumpft und wenn der Leers wieder wieder vollgemacht,
das kann man ja oft ziemlich viele verschiedene Arten und Weisen implementieren.
Könnt ihr euch wahrscheinlich vorstellen. Möglicherweise braucht man dafür
Sprachfeatures, die ihr noch gar nicht kennt.
Und auch jede einzelne Operation, die hier so aufgeführt ist,
da ist relativ viel Text,
wo steht,
wie soll sich
diese Methode verhalten
Gut. Und wir sehen hier drei verschiedene Implementationen. Auswahl,
Permutation, naiv, vielleicht, weiß nicht,
wir können ja einfach reingucken, ob wir uns trauen,
ob wir irgendwas hier verstehen.
Okay, hier wird schon Erres verwendet, das kennen wir noch nicht.
Was haben wir hier? Da werden Listen verwendet,
die lernen wir nächste Woche kennen. Was haben wir hier?
Hier wird wieder ein RE verwendet. Aber ihr seht,
vielleicht jetzt schon, auch wenn wir da nur zwei,
drei Sekunden reingeklickt haben, alleine schon der Zustandsraum von diesen Objekten ist jeweils völlig anders.
Das ist also völlig anders, dass der Programmierer sich Gedanken gemacht hat,
wie kann ich das umsetzen,
dass da jede Zahl nur einmal geliefert wird.
Gut. Und was wir hier aussehen, ist,
dass der, ja, der Klient dieses Interfaces,
das kennt dieses Interface, blöderweise,
das ist jetzt hier so ein bisschen von der sauberen Theorie so ein bisschen weg,
kennt der Effizienzvergleich ja auch die konkreten Exemplare.
Das ist aber nur deswegen, weil er sich diese Exemplare erzeugen muss.
Also wenn wir das jetzt so gebaut hatten, hey,
liebe Effizienzvergleicher, ich stecke die Exemplare rein,
du musst sie nicht selber erzeugen,
dann würde es diese Abhängigkeiten auch nicht geben.
Wir können uns mal ganz kurz angucken,
warum es diese Abhängigkeiten überhaupt gibt.
Also, warum gibt es diese Abhängigkeit zu dem Interface?
Das heißt, dieses, dass der Name dieses Interfaces
hier irgendwo auftauchen muss.
Hier zum Beispiel einmal
Und hier, wenn ich das jetzt irgendwie wegnehmen würde und
irgendwie dafür sorgen würde, dass der Code trotzdem kombiniert,
dann würde es diese Abhängigkeit zu dem Interface nicht mehr geben.
Okay, wie benutzen wir denn hier das Interface? Also,
wir benutzen das Interface einmal als Typ für eine lokale Variable.
Also, der statische Typ, der Variable ZS, ist Zahlensack.
Das sieht man einfach immer bei der Deklaration. Wenn ihr
eine Übung gefragt werdet, wie erkenne ich denn den statischen
Typ einer Referenzvariable? Das ist einfach immer der Typ,
der genau bei der Deklaration links von dem Namen der Variante steht.
In Java ist das zum Glück relativ einfach. Okay.
Das Interessante ist jetzt aber, dass jetzt hier tatsächlich ein anderer Typ steht.
Matthias hatte das auch nochmal sehr schön hervorgehoben.
Bis jetzt kamen wir das immer und da steht Konto K,
gleich ein New-Konto oder Quadrat, Wand,
gleich New-Quadrat oder Kreis, Sonne, gleich New-Kreis.
Und jetzt können wir hier tatsächlich verschiedene Typen hinschreiben, aber es gibt,
es muss eben eine bestimmte Beziehung zwischen diesen Typen geben,
nämlich hier kann ein Interface stehen und hier kann eine Klasse stehen,
die dieses Interface implementiert.
Gut. Und dann seht ihr,
hier später kann ich sagen, ach nö,
jetzt will ich mal auf ein Exemplar einer anderen Klasse zeigen.
Und nö, jetzt will ich mal auf ein Exemplar in einer anderen Klasse zeigen.
Der dynamische Typ der Variablen und ZS ändert sich dadurch,
also ich zeige jeweils auf Exemplare von verschiedenen Klassen,
zu verschiedenen Zeitpunkten.
Der statische Typ ändert sich nie
Okay, wann haben wir hier noch mal? Das Interface
Zahlensack. Hier ist es eigentlich noch ein bisschen spannender,
weil hier konnte ich ja noch so ungefähr im Kontext dieser
Deklaration sehen, was mit dieser Variable passiert.
Wenn ich mir jetzt das mal hier angucke,
ich habe einen formalen Parameter, statischer Typ ist Zahlensack,
und dem wird ja hier nirgendwo was zugewiesen innerhalb dieser Methode.
Also ich habe hier wirklich als Programmierer,
wenn ich hier einfach nur so auf den Quelltext raufgucke und gucke,
was passiert denn hier so an den verschiedenen Kurzzahlen,
kann ich eigentlich nicht wissen, was der dynamische Typ ist,
der Variablensack.
Das wird erst dann feststellen, wenn das Programm ausgeführt wird.
Und das wollte ich euch mal zeigen. Ich implementiere das
nicht, das ist ja hier eure eigene Aufgabe,
ihr solltet mal so ein bisschen Zeitmessung machen.
Ich setze hier einfach mal
einen Haltepunkt
Und ich weiß nicht, ob ihr es schon gesehen habt,
diese Methode vermesse, wird ja einmal hier aufgerufen.
Einmal hier und einmal hier. So, dann machen wir
das mal hier ein bisschen kleiner. Dass ihr das irgendwie
seht, da ist der Haltepunkt. Okay. Jo,
dann das zweite müssen wir so ein Effizienzvergleich haben und starten den mal,
was ich jetzt angebe, ist eigentlich ziemlich egal.
Ich will ja nur mal in diese Methode reinspringen.
So. Programm hält jetzt gerade in dieser Methode an
Ohne großartig über irgendwas nachzudenken, kann ich immer noch sagen,
der statische Typ, der Variable sagt, ist Zahlensack, weil es steht ja hier.
Und wenn ich das im Quelltext nicht erkennen kann,
weil die beiden Zeilen so weit voneinander weg sind oder weiß nicht,
was auf dem Bildschirm ist,
könnte es auch ein Blutjob der Banger hier sehen.
Ihr seht also immer den statischen Typ einer Variable links neben den Namen.
Was ist jetzt der dynamische Typ, der hängt ja davon ab?
Ciao. Das Objekt, auf das diese Referenzmariaple zeigt,
ist ja von einer gewissen Klasse.
Und dann schauen wir uns das Objekt auch einfach mal an.
Ich klicke da einfach doppelt drauf. Und dann sehen wir
jetzt das Objekt. Kann mir irgendjemand von euch verraten,
wo ich jetzt sehe, was der dynamische Typ ist,
der Variable Sack
Ja. Oben in dem roten Fenster, und zwar sehr weit oben,
weil wir sehen ja immer in Blue Jail,
von welcher Klasse des Exemplars, das ich mir gerade angucke.
Ja, der dynamische Typ ist also naiv, dann schließe ich das mal.
Sag mal, mach doch mal weiter im Programmauflauf,
bist du mal wieder auf so einen Haltepunkt triffst.
Jetzt sind wir hier zum zweiten Mal reingegangen.
Und jetzt sehen wir, jetzt zeigt diese Variable,
die immer noch den statischen Typ Zahlensack hat,
zeigt sie jetzt auf ein Objekt der Klasse per Mutation.
Der dynamische Typ ist also per Mutation.
Okay, dann machen wir das nochmal.
Jetzt schaue ich mir an, auf was zeigt es jetzt.
Jetzt zeigt es auf eine Exemplar der Klasse Auswahl.
Das heißt, der dynamische Typ ist Auswahl.
Eigentlich ist das relativ einfach, um mit Blue Jail
finde ich auch so recht anschaulich
Okay. Gut, jetzt habe ich noch eine Viertelstunde.
Dann switche ich jetzt noch ein bisschen hin und her.
Also ich hatte ja schon gesagt, in dieser Aufgabe hat
man so diesen Gedanken, Interface als Spezifikation von Verhalten.
Man kann, Interface ist aber auch,
ich öffne es erstmal, bevor ich drüber rede,
das wollte ich, glaube ich, öffnen, genau.
Wenn wir uns dieses Interface angucken, dann ist das eigentlich nur
ein sehr, ich sag mal, technisch motiviertes Interface.
Es ist also, da steht eine Operation drin,
die Klassen müssen das irgendwie implementieren. Aber hier wird nirgendwo gesagt,
was da eigentlich genau verlangt wird von den Klassen.
So halb, so ein bisschen halb weich,
halb hart
Okay. Interface, Dateiverarbeiter, ein Dateiverarbeiter, verarbeitet an
einem Datei. Das ist doch mal ein toller Kommentar.
Wer hat denn das verbrochen? So. Wir sehen also,
es gibt nur eine einzige Operation in diesem Interface, verarbeite.
Und der Parameter ist ein Pfeil. Ein Pfeil ist einfach
nur irgendein interner Java-Typ, mit dem man auf Dateien zugreifen kann.
Okay. Ein Dateiverarbeiter, verarbeitet eine Datei. Okay.
Dann sollten wir uns vielleicht mal den Klienten angucken von diesem Interface.
Was macht er denn mit diesem Interface?
Also der Klient ist der sogenannte Verzeichniswanderung
Habe ich da was Schönes geschrieben? Ein Verzeichniswanderer wandert rekursiv
durch alle Dateien eines Verzeichnisses. Ach, das ist doch irgendwie
ganz nett. Vielleicht benutzen wir den einmal ganz kurz.
Und dann ist euch vielleicht erstmal klar,
was überhaupt der Sinn dieser Klasseverzeichniswanderer ist.
Nimm mal diesen Konstruktor. So, was kann ich denn
an dem Verzeichniswanderer aufrufen? Ich kann ihn starten.
Und muss jetzt einen Dateiverarbeiter übergeben. Ich weiß nicht,
ob ihr schon mal einen Blutjoy interaktiv gesagt habt,
ich will eine Methode aufrufen, die ein Objekt erwartet.
Dann wäre jetzt wahrscheinlich die erste Intuition,
dann erzeuge ich mir doch mal einen Dateiverarbeiter.
Aber Matthias hat euch ja schon gesagt,
man kann von Interfaces
keine Exemplare erzeugen
Tja, was mache ich denn stattdessen? Also ich brauche hierfür
die Startmethode einen Dateiverarbeiter. Ich kann mir aber keinen Dateiverarbeiter erzeugen.
Ist jetzt Game Over oder was mache ich jetzt?
Ja. Ich warte jetzt mal kurz auf Lister zum Beispiel.
Genau, wir können uns ja Exemplare erzeugen von Klassen,
die dieses Interface implementieren. Also die quasi dieses Versprechen einlösen
, so eine Methode zu haben. Das mache ich einmal
ganz kurz. Und wenn das dann funktioniert,
schauen wir uns an, wie es gebaut ist.
Also ich starte jetzt und übergebe als Dateiverarbeiter,
die sind Kurzaufliste
Jo, da scheint jetzt irgendwas Sinnvolles zu kassieren.
Der ist also irgendwie durch mein ganzes Dateisystem durchgegangen und hat
dann was gemacht. Okay. Was macht denn dieser Kurzauflister
? Ihr seht, das ist ja extrem einfach.
Also, die Klasse Kurzauflister implementiert das Interface-Dateiverarbeiter. Das bedeutet,
diese Operation verarbeite Datei, die muss es hier irgendwo geben.
Und sie ist auch nicht schwierig zu finden,
weil hier ist ja nur insgesamt eine Methode.
Das ist also diese, ne? Also hier habe ich
jetzt quasi jemand Versprechen eingelöst. Hier verspreche ich,
dass ich das Interface implementiere. Und hier tue ich es
dann auch tatsächlich. Weil wenn ich das nicht mache oder
das irgendwie anders nenne, dann wird der Compiler
sich beschweren
Also diese merkwürdige Fehler hier unten mit Appstract und Overwrite und so,
das würde euch jetzt noch nicht so viel sagen.
Es sagt einfach Folgendes. Lieber Programmierer,
du hast doch hier versprochen, das Interface zu implementieren.
Ich habe aber nirgendwo diese Methode gefunden, verarbeiteter Teil.
Da hast du dich wohl irgendwie geirrt. Das ist irgendwie
ganz nett, dass der Compiler das tatsächlich für uns prüft.
Okay. Gut, dann schauen wir uns einmal ganz kurz
den Verzeichniswanderer an, da waren wir ja schon drin.
Nee, das ist der Effizienzvergleich hier.
Was macht er eigentlich? Oh, das ist ja spannend
. Er hat eine Exemplarvariable vom Typ Dateiverarbeiter
Das heißt also, der Verzeihniswanderer weiß nicht,
was für ein konkretes Exemplar sich dahinter verbergen wird,
erkennt nur das Interface, wo benutzen wir denn diesen Dateiverarbeiter?
Wo wird er benutzt? Einmal wird er hier, ja,
findet ihr eine Zuweisung statt, das hatten wir eben gesehen,
wenn ich die Startmethode aufrufe, muss ich einen Dateiverarbeiter übergeben.
Der Dateiverarbeiter wird sich ja einfach nur entsprechend gemerkt,
okay, dann wird ja die Untersuchermethode aufrufen,
die finden wir hier und mehr gibt es auch nicht mehr
Gut, versuchen wir mal ganz kurz zu verstehen,
was hier passiert. Also ich sage, nach dem Verzeihnis,
in dem ich gerade bin oder das hier übergeben wurde,
gib mir doch mal alle Dateien, die in dir drin sind.
Und wir werden diese Syntags nächste Woche noch kennenlernen,
was das genau bedeutet. Also das heißt einfach nur,
ich möchte für jede Datei die in diesem Verzeichnis ist,
möchte ich einmal diesen Schleifenrum vorausführen. Gut,
dann gibt es hier noch so ein paar Sicherheitsüberprüfungen,
also ob die Datei sichtbar ist, ob ich Leserechte habe und so,
das ist, weil sonst stürzt es manchmal ab,
wenn man versucht auf Dateien zuzugreifen, auf die man nicht zugreifen darf.
Und dann ist hier eine recht spannende Stelle,
die so ein bisschen an nächste Woche, letzte Woche anknüpft.
Ich sage also, wenn es in dem Verzeichnis,
in dem ich gerade bin, Eine Datei gibt,
die auch ein Verzeichnis ist, dann möchte ich dieses Verzeichnis untersuchen.
Sieht jemand, was diese Zeile Kurb mit letzter Woche zu tun hat?
Da hinten? Sie ist reklusiv, sehr schön.
Also um ein Verzeichnis zu untersuchen, muss ich alle Verzeichnisse in
dem Verzeichnis untersuchen. Und das wird irgendwann zum Glück aufhören,
weil Verzeichnisse sind nicht unendlich tief geschachtelt sein können im Interteisystem.
Das ist also das von letzter Woche.
Und dieser Punkt ist sehr interessant für die heutige Woche.
Die heutige Woche, sagt man das so?
Die aktuelle Woche
Gut, ich rufe also auf diesem Dateiverarbeiter,
rufe ich jetzt diese Operation, verarbeite auf.
Dieser Verzeihniswanderer hat jetzt aber keine Ahnung, was da passiert.
Denn wenn ich mir angucke, einfach nur rein statisch,
okay, Dateiverarbeiter war ja hier oben definiert,
Typ ist Dateiverarbeiter, schauen wir uns den einmal ganz kurz an.
Schauen wir uns mal hier die Implementation an.
Tja. Da kann ich nicht sehen, was passiert,
weil hier ist ja kein Code, der ausgeführt werden könnte.
Es kommt also immer darauf an, hat der Klient
beim Staaten ein Exemplar dieser Klasse übergeben?
Oder hat er ein Exemplar dieser Klasse übergeben?
Das ist ja bei dem Verzeichniswanderer
völlig egal
Okay. Ja, und das ist auch schon der ganze Verzeihnis, Mandan.
Das heißt, der Sinn von den Interfaces ist hier,
man kann das sicherlich auch für Arbeitsteilung verwenden und so,
das schließt es sicherlich nicht aus. Aber der Sinn ist es,
dass ich sagen kann, ich habe irgendwie einen Algorithmus,
da ist schon alles implementiert, um zu sagen,
ich gehe durch die Verzeichnisse und das passiert regulsiv und ich greife nicht auf Dateien zu,
auf die ich nicht zugreifen darf.
Aber was soll ich denn dann jetzt mal mit einer Datei machen,
wenn ich tatsächlich eine Datei gefunden habe und was damit gemacht werden soll,
das kann ich jetzt sozusagen parametrisieren,
indem ich entweder ein Exemplar dieser Klasse übergebe,
da wird einfach nur der Dateiennahme auf die Konsole geschrieben oder
indem ich zum Beispiel ein Exemplar dieser Klasse übergebe,
da wird dann sogar der komplette Pfad übergeben
Ich will also nicht nur wissen,
wie meine ganzen Dateien heißen im Dateisystem, sondern ich will auch,
dass auf der Konsole steht, wo diese Datei ist.
Und wenn euch jetzt vielleicht noch was Schönes einfällt,
wenn ihr sagt, nein, ich will auch, dass angezeigt wird,
wie groß ist die Datei oder wer hat sie erzeugt oder welche Schreib- und Leserechte hat sie,
dann baut ihr euch einfach eine eigene Klasse,
wo dieses verarbeitete Datei entsprechend implementiert ist und benutzt das dann.
Das ist irgendwie, tja, auch ein sehr netter Anwendungsfall
für Interfaces. Okay. Habt ihr dazu noch Fragen?
Nee, ich sehe keine Fragen. Gut. Was gucken
wir uns denn dann mal als nächstes an
Ah, ich habe so viel vorbereitet. Wollen wir auch
mal selber was programmieren, sonst gucken wir uns nur Dinge an.
Das wäre so ein bisschen schade. Ampeln. Das ist
ein älteres Projekt, hoch, wo ist es,
ich habe es noch gerade geöffnet, hier ist es.
Ich weiß nicht, ob ihr euch noch erinnert. Nochmal kurz als Erinnerung,
oder falls ihr da gerade nicht da wart,
es ging ja hier so ein bisschen um bulische Algebrau und hier waren irgendwelche grafischen Oberflächen schon vorgegeben.
Und ja, vielleicht hätte ich mal die Musterdosen mitbringen sollen,
wo alle schon funktioniert. Okay. Das Interessante hier ist,
wenn man sich mal die Ampel anschaut,
und ich schaue mir jetzt mal hier die Dokumentation an,
dann sieht man also folgendes,
es gibt hier irgendwie vier Methoden, ich kann fragen,
ob die gelbe Lampe leuchtet,
ob die grüne leuchtet,
ob die rote leuchtet und ich kann weiterschalten
Und wenn ich mir mal die Dokumentation angucke von der Bimpel
, dann sehe ich Folgendes. Sie hat vier Methoden.
Ich kann fragen, ob die gelbe Lampe läutet,
ob die grüne leuchtet, ob sie rote leuchtet.
Und ich kann sie weiterschalten. Und wenn ich mir die
Zimpel angucke und auf Dokumentation umschalte, was ist da?
Vier Methoden. Ich kann fragen, die gelbe leuchtet,
ob die grüne Leute, ob die rote läutet und ich kann sie weiterschalten.
Das heißt also, die Schnittstelle dieser Klassen ist eigentlich exakt dieselbe.
Man könnte jetzt also hingehen und ein Interface über diese drei Klassen bauen und sagen,
alle diese drei Klassen implementieren das Interface und in dem Interface
würden dann diese vier Operationen stehen, leuchtet gelb,
leuchtet grün,
leuchtet rot und schaltet weiter
Und welchen Vorteil hätte das jetzt? Das würde den Vorteil
haben, dass ich nur noch eine Google-Klasse brauche.
Denn wenn ihr euch mal diese Googi-Klassen anguckt,
die sind extrem ähnlich. Die unterscheiden sich nur darin,
welche konkreten Ampelbimpel oder Zimpel sie erzeugen.
Das will ich euch einmal ganz kurz demonstrieren, dass es
wirklich so ist. Gehen wir uns zu Platz 5. Genau,
zum Beispiel die Ampel Google und die Bempel Google.
Nicht Ampel. Und die Bimpel Google.
Was habe ich falsch gemacht auf jedes Java
So, hier wurde mir jetzt also gesagt,
wie unterscheiden sich diese, oh guck mal,
es passt auf einen Bildschirm, ich muss noch nicht mal wirklich scrollen.
Wie unterscheiden sich jetzt diese Klassen? Wir müssen die gar nicht
so unbedingt verstehen, weil das Erste, was sich unterscheidet,
ist hinter dem Schlüsselwort Klaas steht jeweils was anderes.
Das ist irgendwie klar, da muss ja nun mal der Name der Klasse stehen.
Gut, dann, was schon ein bisschen spannender ist,
die Ampel Gui benutzt eine Ampel und die Bimpel Gui benutzt eine Bimpel.
Das ist relativ spannend für uns, oder kann man da extrahieren,
was ein Interface-Typ stattdessen einbauen? Dann ist klar,
dass der Konstruktor an, das heißt,
der Konsote muss ja heißen wie die Klasse,
das ist jetzt nichts Besonderes, das werden wir gleich wahrscheinlich rausbauen müssen,
das Erzeugen der Ampeln,
das will ich gar nicht in der Gui-Klasse haben
Was haben wir hier? Das hat auch was damit zu
tun. Und das ist nur noch, wie das Fenster
oben heißen soll, das ist eigentlich auch nicht so spannend.
Also der spannende Punkt ist tatsächlich eigentlich der hier.
Wie ist die Abhängigkeit von der Gui-Klasse zu der Logik?
Die ist hier hart über Klassen kodiert. Und ich würde
das eigentlich gerne so ein bisschen entzerren und sagen,
ich schalte da mal ein Interface zwischen.
Wir gucken mal, ob ich das schaffe in der Zeit.
Ich habe ja noch ungefähr fünf Minuten.
Gehen wir mal wieder zurück auf den Source Code.
Ich kopiere mir jetzt einfach mal den kompletten Source Code.
Ich habe mir leider nicht viel Zeit. Und wenn man
jetzt ein New Class hat, kann man hier auch Interface anklicken,
ich nenne das jetzt mal irgendwie Ximple vielleicht,
ob es, entschuldige bitte,
wenn der Name vielleicht nicht
so schön ist
Dann paste ich da jetzt mal alles rein und ersetze das Schlüsselwort Klaas durch Interface,
dann muss ich sämtliche Zustandsfelder rausnehmen bei Interface,
das haben ja keinen Zustand, dann muss ich da Konstruktoren haben,
sie auch nicht, ich kann ja keine Exemplare erzeugen,
dann Methodenrümpfe gibt es auch nicht, das heißt,
ich mache hier Mansenmikron und statt eine Methodenrumpfe.
Danke, es kommen sehr komische Effekte,
wenn man das vergisst, das ist mir schon mal passiert.
So, wir haben jetzt also sämtliche Implementationen hier rausgeschnitten,
wir haben nur noch gesagt, wie ist der Umgang mit einer Ximple?
So, wir haben jetzt dieses Interface gebaut,
wir haben das quasi extrahiert aus den Drahtklassen.
Alle haben eine gemeinsame Schnittstelle, dann lass uns doch mal
ein Interface dafür bauen. Dann behaupten wir jetzt mal,
dass diese Klassen das auch tatsächlich machen, also Implemente,
Implements, Ximple, also der Compiler, das ist ja akzeptiert, ja.
Ich will mich also nirgendwo vertrauen. Ihr seht dann auch,
dass da neue Pfeile entstehen. Der Compiler merkt also,
ah, diese Klasse implementiert das Interface,
dann baue ich mal hier so einen Pfeil rein.
Das macht Bluedy alles für euch automatisch.
So, okay. Jetzt bin ich mal mutig,
jetzt lösche ich mal diese Klasse.
Und die lösche ich jetzt auch
Und jetzt schaue ich mal in diese Klasse, an diese Google-Klasse,
ob ich da jetzt mal noch die entsprechenden Stellen finde.
Also, ich will keine Abhängigkeit mehr haben,
keine statische Abhängigkeit zu der konkreten Klasse Ampel,
sondern ich nehme das jetzt, ich sage jetzt,
ich will nur noch über das Interface zugreifen. Gut,
dann habe ich natürlich nicht mehr den Luxus zu sagen,
ich kann mir so eine Google einfach erzeugen und die erzeugt sich ihre Ampel selber,
weil die soll ja nicht wissen,
was für ein Ampel das sein soll, das muss ich jetzt leider löschen.
Dann sage ich, wenn ein Klient eine Google erzeugt,
muss er mir schon irgendwie mitteilen,
um welche Ampel es sich handelt,
also verwende ich hier mal den Interface-Typ.
So, muss ich sonst noch
irgendwas ändern?
Das ist doch schön. Den ganzen Rest müsst ihr auch
nicht verstehen. Gui-Programmierung kommt ja erst nächstes Semester. So,
jetzt seht ihr also, effektiv ist die Anzahl Klassen
sogar geschrumpft, obwohl ich das Interface hinzugeführt habe.
Ich hatte vorher sechs Typen, und jetzt habe ich nur noch fünf.
Das ist doch nett. Warum gibt es jetzt hier noch
diese Abhängigkeit zur Ampel? Das will ich eigentlich nicht.
Taucht hier irgendwo das Wort Ampel auf in Großbuchstaben, Match Case.
Kann ich auch sagen, ich will das ganze Wort nur sehen, nee.
Taucht hier irgendwo tatsächlich noch der statische Typ Ampel auf.
Nee, das ist einfach ein Fehler.
Manchmal erkennt Blut ja nicht, wenn Abhängigkeiten verschwinden,
dann lösche ich den Files einfach und übersetze nochmal komplett neu
Reworld Package. Okay, die Abhängigkeit war nicht mehr da,
dass er Budget noch nicht verstanden hat.
Gut. Ich kann jetzt so eine Ampel... Ach,
habe ich jetzt alle Konstruktoren gelöscht? Auch der ist private
. Alles klar. Hubbleck. So. Jetzt haben wir wieder
so ein ähnliches Problem wie eben mit dem Verzeichniswanderer.
Wenn ich mir eine Guya zeugen will, muss ich der Guy sagen,
ja, wie kann sie denn kommunizieren mit der Logik?
Über so eine Xympel. Jetzt kann ich aber von so
einer Xympel keine Exemplare zeugen. Das ist ja nur ein
Interface. Was muss ich jetzt machen? Ja. Genau
, machen wir mal
eine Ampel
Jetzt erzeuge ich mir eine Gui und stopfe da diese Ampel rein.
Es wird eine Ximple erwartet, ich will eine Ampel übergeben,
Ampel implementiert das Interface Ximple, kein Problem.
Und hier ist unser Gurium,
das Weiterschalten auf Game funktioniert auch und der Rest funktioniert auch nicht,
weil wir es noch nicht eingebaut haben.
Wenn ich das Ganze jetzt mal mit einer Bimpel mache,
ich glaube, da war das Verhalten irgendwie anders,
möglicherweise hat da gar nichts funktioniert, ich weiß es gar nicht mehr genau.
Schauen wir mal kurz. Also nicht mal das weiterschalten hat funktioniert und ich glaube,
bei der Zimpel war es noch schlimmer, oder?
Das ist jetzt aber nur, weil es nur nicht implementiert ist.
Okay, da wird es einmal schwarz und dann passiert
gar nichts mehr
Ja. Das finde ich tatsächlich ganz nett. Das hat
mir jahrelang in den Fingern gejuckt. Immer wenn ich
dieses Ampel-Projekt gesehen habe, habe ich gesagt, ah,
da könnte man doch so schöne Interfaces mit motivieren.
Und da habe ich das jedes Jahr vergessen.
Und diesmal habe ich das erste Mal dran gedacht.
Also nochmal als Wiederholung. Ampel, Bimpel und Simpel,
haben exakt dieselbe Umgangsform. Wenn man sich die Schnittstelle anguckt,
die Dokumentationsansicht, der Umgang mit diesen Typen ist immer exakt derselbe.
Also haben wir jetzt ein Interface extrahiert,
wo dieser Umgang formal in Java festgehalten wurde.
Und dann kann ich sozusagen auf die Code-Duplizierung,
auf der Ebene darüber verzichten,
weil ich jetzt nur noch mit dem Interface arbeite.
Fragen? Keine Fragen.
Dann bis nächste Woche