Title: FOOP-Exam2
Tags: FOOP-1 FOOP-6 FOOP-2 NOT FOOP-3

Q: Prinzip der Ersetzbarkeit?
A: Typ S ist Untertyp von Typ T wenn,
jede Instanz (Objekt) von S dort verwendet werden kann, wo eine Instanz von T erwartet wird.
Das Prinzip der Ersetztbarkeit gilt wenn:
> Typ von Eingabeparametern sind kontravariant
(sinnvoll sind also nur invariante Eingangsparameter)
> Typ von Variablen und Durchgangsparametern (Referenzparameter) sind invariant
> Typ von Konstanten, Ergebnissen und Ausgabeparametern sind kovariant
entsprechende Methoden in S zeigen das gleiche Verhalten wie die in T

Q: Ersetzbarkeit (Untertypbeziehung) und Methoden Verhalten?
A: Methoden im Untertyp S zeigen das gleiche Verhalten wie die im Obertyp T
WENN:
vom Client zu erfüllende Zusicherungen (=Vorbedingungen) in S nicht stärker als die in T
Zusicherungen, die vom Server erfüllt werden müssen (= Nachbedingungen, Invarianten) in S nicht schwächer als die in T
Zusicherungen, die von Client und Server erfüllt werden müssen (nur sinnvoll für Invarianten, in einigen Fällen History Constraints) äquivalent in S und T
Methoden in S werfen nicht mehr Exceptions als die in T (in entsprechenden Situationen)

Q: Ausdrückbarkeit von Zusicherungen?
A: In der Theorie:
alle Zusicherungen sind formal ausdrückbar (Logik, Algebra),
Beziehungen zwischen Zusicherungen statisch überprüfbar
In der Praxis:
viele Zusicherungen sind nicht ausdrückbar und Beziehungen nicht überprüfbar, weil
> die Programmiersprache keine ausreichende Unterstützung bietet (informelle Kommentare, meist mehrdeutig).
> Clients verfügen nicht über genügend Informationen über den Objektstatus (Data hinding im Konflikt mit Design-by-Contract)
> Objektstatusänderungen in nicht vorhersehbarer Weise (Parallelität, Aliasing)
es gibt auch packages für Java um Zusicherungen zu programmieren, aber dann erst zur Laufzeit überprüft, obwohl Zusicherungen etwas statisches sind daher meeh

Q: Mögliche Probleme beim Schreiben von Zusicherungen? (an einem Beispiel)
A: +
„immediately after“ ist mehrdeutig
Übliche Interpretation: Element bleibt im Set, solange kein „delete“ aufgerufen wird (das in einem Untertyp definiert werden könnte)
(Vorbedingungen besonders anfällig dafür, weil der Client überprüft; wenns geht daher eher über Nachbedinungen, die sind viel mächtiger << wichtiges Learning)
Der Konstruktor, der „set“ erstellt, könnte Aliase (anders als „set“) eingeführt haben, was dazu führt, dass „do_something_not_using_set“ das Set ändert
→ „b“ möglicherweise falsch
gleichzeitiger (concurrent) Thread (im Konstruktor erzeugt) könnte 1 löschen
→ „a“ und „b“ möglicherweise falsch

Q: Lösungen für die Probleme beim Schreiben von Zusicherungen? (an einem Beispiel)
A: „find“ aufrufen, um zu überprüfen, ob das Element im Set enthalten ist
→ oft ist die Lösung schlecht, weil nichts sinnvolles zu tun ist, wenn der "find" Check fehlschlägt
Aliasing gänzlich verhindern/ausschließen
→ schlechte Lösung, da extrem teuer
(weil Aliasing eines der mächtigsten Werkzeuge in OOP ist)
bei Nebenläufigkeit immer atomare Aktionen
→ oft eine gute Idee, aber manchmal sehr teuer
unerwartete Seiteneffekte (z. B. im Konstruktor) vermeiden
Die Frage ist, was genau ist "unerwartet", man muss so denken wie ein Anwender/Client!
→ die beste, sinnvollste Lösung
nützlich, aber dann sind Erwartungen kaum jemals formal ausdrückbar

Q: History Constraints? Beispiele für History Constraints?
A: Tipp: alle verfügbaren Informationen nutzen, egal wie leicht sie als übliche Zusicherungen (Vor-, Nachbedingungen, Invarianten) ausgedrückt werden können.
History Constraints schränken die Entwicklung (evolution) von Objekten auf eine Weise ein, die kaum durch übliche Zusicherungen ausgedrückt werden kann
Beispiele:
> „der Wert von x kann sich nur erhöhen“ (Server verantwortlich),
> „‚Unlock‘ nur nach ‚Lock‘ aufrufbar“ (Client verantwortlich - nur der Client kann hier kontrollieren, was er aufruft)
History Constraints, bei denen der verantwortliche Server Invarianten ähnelt; diese können in Untertypen restriktiver sein.
History Constraints wo der verantwortliche Client die Aufrufsequenzen einschränkt; Untertypen können mehr Aufrufsequenzen unterstützen als Supertypen.

Q: Empfehlungen für Zusicherungen?
A: > das Ziel ist: so wenig Zusicherungen wie möglich
> unter Berücksichtigung aller Möglichkeiten zu teuer; nicht versuchen
> berechenbar sein, Tricks vermeiden, auf „normales“ Verhalten verlassen
NICHT: „Wie könnte man das noch interpretieren oder umgehen?“
> Designregeln verwenden (spezifisch für Unternehmen oder Projekt)
> alle verfügbaren Informationen als Zusicherungen verwenden (History Constraints)
Wichtigste Regel: unnötige Abhängigkeiten vermeiden!
- keine vermeidbaren Zusicherungen,
- keine unnötigen Aufrufe und Parameter,
- keine unnötige Sichtbarkeit von Variablen und Methoden,
- nur wohlüberlegte Parametertypen
- Code nur auf der untersten Ebene der Klassenhierarchie (= Vererbung vermeiden)
(Unterschied Vererbung und Subtying, Vererbung interessiert uns nicht, wenn sinnvoll programmieren)

Q: Bedeutung von Namen?
A: > Namen machen den Unterschied aus zwischen strukturellen und nominalen Typen
> Namen sind Abstraktionen von Objektverhalten, Methodenverhalten und Variableneigenschaften und ähneln damit informellen Zusicherungen.
> Namen unterstützen die Intuition;
Kommentare sind nur dann notwendig, wenn die Intuition nicht ausreicht
→ „gute Programme auch ohne Kommentare lesbar“ ??
> Intuitive Namen dadür da, dass Programmierer vorhersehbar sind
(schlechte Namen → kein Vertrauen → unnötiger Code)
> Namen von Typen liefern semantische Informationen (nicht nur für Programmierer, auch für Maschinen, auch für den Compiler)

Q: Strukturelle, nominalle Typen, was bedeutet das?
A: Zwei strukturelle (= anonyme) Typen in Untertypenbeziehung:
{ String name; String address() }
{ String name; String address(); int regNr }
(Notation von Record/Struct)
> Hier ist keine Abstraktion dahinter, Compiler erkennt automatisch, dass das eine in Untertypbeziehung zum anderem steht wegen gleicher Struktur, auch wenn wir das nicht wollen oder es nicht so ist.
> Strukturelle Typen überall dort wo wir keine Abstraktion haben
> Strukturelle Typen können Nominale Typen simulieren:
{ String name; String address(); void isPerson() }
{ String name; String address(); void isPerson(); int regNr; void isStudent() }
aber unbeabsichtigte Gleichzeitigkeit ist möglich (kann gefälscht werden)
> In der Theorie verwenden wir hauptsächlich Strukturtypen, in der Praxis verwenden wir hauptsächlich nominale (= benannte/named) Typen.

Strukturelle vs. Nominale Typen (Vergleich)

Q: Erklären wie nominale Typen Eigenschaften sicherstellen?
A: Beispiel:
class SortedList<A extends Comparable<A>> { ... }
> Eigenschaft „sortiert“ kaum direkt überprüfbar, außer durch Klassenzugehörigkeit
> Jede Instanz von SortedList<T> wird von einem Konstruktor in SortedList und seinen Unterklassen initialisiert (eine Vortäuschung ist fast unmöglich)
> Wenn SortedList und seine Unterklassen die Eigenschaft „sortiert“ sicherstellen, können wir uns darauf verlassen
> durch die Verwendung nominaler Typen wird ausgeschlossen, das es etwas anderes als sortiert ist

Q: Vorteile von strukturellen Typen?
A: - man will nicht, dass Eigenschaften garantiert werden (nominal) sondern, dass die Struktur gleich ist, z.B. es reicht aus, dass es eine compareTo Methode gibt, egal welche genauen Eigenschaften die jetzt hat
- als Schranken für gebundene Generizität
- bei der Verwendung von Plug & Play auf Softwarekomponenten
- bei der Einführung von Obertypen von bereits bestehenden Untertypen

Q: Wo stören Namen manchmal?
A: - als Schranken bei Generizität
- Plug & Play
- Einfügen gemeinsamer Oberklassen (Obertyen) von exisiterenden Untertypen
gilt für nominale Typen, daher strukturelle Typen

Q: Generizität mit anonymen Schranken? Wie funktioniert es?
A: Java-Beispiel:
class SortedList<A extends Comparable<A>>
kein Subtyping wird hier angewendet, nur matching (higher order subtyping), wegen dem sind binäre Methoden möglich
Warum muss A von Comparable<A> erben?
Einzige Voraussetzung: A stellt Methoden von Comparable<A> bereit, da Comparable<A> keine weiteren Eigenschaften impliziert
Beispiel in Ada, das die Verwendung anonymer Grenzen zeigt:

Q: Arten gebundener Generizität?
A: Es gibt 2 Ansätze, die man unterscheiden kann
keiner der beide ist besser als der andere
in Java, C# genutzt
basierend auf einem Modell "System F-bound", das Modell beschreibt Sprachen, funktionallen Sprachen, erweitert auf OOP
rekursive Definition:
S          ≤ T(S)
S ist ein Typparameter z.B. Integer
Java-Beispiel:
Integer ≤ Comparable<Integer>
(Untertyp Beziehung = Beziehung auf Grenzen)
binäre Methoden (2 Inputparameter vom selbem Typ z.B. compareTo) können definiert werden, allerdings nur für eine Vererbungsebene (inheritance level)
wenn man so etwas hat: Integer ≤ Comparable<Integer> will man meist eine binäre Methode definieren (compareTo)
in Haskell, C++ genutzt
wird auch Matching (Relation) genannt (bei Funktionen über Typen)
S <# T (durchgestrichenes =; S ist higher order Untertyp von T)
unterstützt binäre Methoden direkt, aber keine Ersetzbarkeit (Hauptunterschied zwischen Subtypisierung höherer Ordnung und Subtying: keine Ersetzbarkeit; verstößt gegen das Ersetzbarkeitsprinzip)
Binäre Methoden sind nicht mit Ersettzbarkeit kompatibel, wegen kontravarianten Inputparameter (man brächte kovariante Inputparameter)
statisch typsicher aufgrund der Generizitätsbindung
(Untertyp Beziehung (∕= NICHT GLEICH) Beziehung auf Grenzen)

Q: Dynamisches Binden mit Einfachvererbung?
A: > Jedes Objekt enthält eine Referenz auf dynamischen Typ
> dieser Typ enthält ein Array von Referenzen auf Methoden (= VFT = virtuelle Funktionstabelle, virtual function table)
> Um eine Methode aufzurufen, schaut man im Objekt nach und verweist dann auf VFT und die hat einen Verweis auf den eigentlichen Code der Methode
> Zum Beispiel eine class files in Java, enthält den gesamten VFT- und Methodencode usw (alles zusammen)
> interne Darstellung der Methode = VFT-Index - eindeutige Zahl,
> interne Darstellung der Variablen = Offset von der Objektadresse - Zahl
(man hat viele indirekte Adressen - Hardware ist optimiert dafür)
> Einfachvererbung: VFT und Variablenoffsets von der Oberklasse übernehmen, am Ende zusätzliche Methoden und Variablenoffsets anhängen,
vererbte Methode = kopierter Eintrag in VFT,
überschriebene Methode = geänderter Eintrag in VFT
> VFT – unterschiedliche Kopiertabelle für Untertyp wie für Obertyp; unterschiedliche Tabellen, aber gleiches Layout
-> siehe Zeichnung <-

Q: Dynamisches Binden mit Interfaces?
A: > Einfache Implementierung nicht für Mehrfachvererbung verwendbar, da unterschiedliche VFT-Indizes für dieselbe Methode geerbt werden
> Daher für jedes implementierte Interface (außer eines) eine separate VFT verwenden.
> außer eins, da für einen Typ kann der VFT Index vom Objekt verwendet werden, wenn nur Einfachvererbung für Interface vorhanden ist
> Mehrere Verweise auf VFTs im Objekt oder im primären VFT (Referenz direkt im Objekt früher in alten Interpretern, aber nicht mehr).
> Der deklarierte Typ bestimmt den zu verwendenden VFT.
Negative Indizes zum primären VFT hinzufügen mit den Verweisen auf die anderen VFT(s),
unterschiedliche VFTs für jede Klasse (= implementiertes Interface)
Einfachvererbung durch Anhängen zusätzlicher Methoden,
Mehrfachvererbung durch Hinzufügen weiterer VFTs
-> siehe Zeichnung <-

Q: Dynamisches Binden mit Mehrfachvererbung?
A: Wie bei Interfaces, aber Objektlayouts müssen berücksichtigt werden (Objekte auch weil Variablen, bei Interface kann es keine Variablen geben)
C++: Unterklasse verwendet Objektlayouts von Oberklassen als Objektteile (notwendig für einfache up-casts)
Diamantvererbung ist ein Problem bei dynamischen Binden und Vererbung
-> siehe Zeichnung <-
Ein up-cast ändert die Objektreferenz zum Anfang des Objektteils (Hinzufügen von Delta)
Jeder Objektteil hat seine eigene VFT und merkt sich sein Delta (oder in derselben VFT, macht keinen Unterschied)
Delta muss subtrahiert werden, bevor Referenzen auf Identität verglichen werden
Delta im alten C++, heutzutage reine virtuelle Funktionen (ähnlich zu Java -> abstrakte Methode), kein Delta mehr erforderlich

Q: Methodenaufruf bei Mehrfachvererbung? Thunks?
A: Normalerweise ist das der Ablauf (sehr vereinfacht):
> angenommen: Delta ist irgendwo im table register gespeichert
> #selectorOffset = die Nummer/Index der Methode
Delta = 0 in den meisten Fällen (fast allen)
> Delta muss nicht zur Laufzeit berechnet werden, Compiler weiß Delta
> Delta ist in VFT gespeichert, für Vergleich von Objektreferenzen
> Delta braucht man nicht wirklich für Methodenaufruf
→ Tipps zur Optimierung:
Anweisungen 2 und 4 weglassen und zu Thunk springen (= überschriebene Methode, wenn Delta /= 0)
Thunk (was macht die zusätzliche Methode):
add objectReg, #delta, objectReg
jump #method
-> siehe Zeichnung <-
> eine Methode zu überschreiben (overwrite) kostet nichts
es kostet viel, diesen Thunk zu verwenden

Q: Weitere Optimierungsmöglichkeiten für dynamsichen Binden/Methodenaufrufe?
A: wenn die Methode statisch bekannt ist
-> direkt zur Methode springen, anstatt die VFT zu verwenden,
-> Methoden-Inlining möglich, um weitere Optimierungen zu ermöglichen (z.B. bei Gettern, keine Invokation nötig), auch möglich inline nur ersten Block einer Methode
Methoden sind eher statisch bekannt, wenn der Compiler die Typhierarchie kennt (C++ ! in Java kennt der Compiler nru das Object mit dem er gerade arbeitet, keine Hierarchie)
Optimierung 1
Teilweises Inlining durch Thunks möglich (jump von Thunk zu Originalmethode immer gleich, daher kann man Methode in Thunk inlinen)
-> siehe Zeichnung <-
Optimierung 2
Thunks (mit Delta /= 0) sind fast immer vermeidbar, wenn VFT in beide Richtungen wächst und der Compiler die Typhierarchie kennt (ist nur einmal anwendbar, weil oben und unten dann kein Platz mehr)
-> siehe Zeichnung <-
Optimierung 3
dynamisches Caching (hauptsächlich in dynamischen Sprachen z.B. Smalltalk, in anderen Sprachen ist dynamisches Binden schon sehr optimiert):
-> Springe zuerst direkt zur gleichen Methode wie beim letzten Aufruf (man hat Variablen gespeichert in jedem Teil von Code wo Methoden aufgerufen werden, mit der Adresse der Methode die zuletzt aufgerufen wurde an der Position)
-> dann die Klassen vergleichen und erneut springen, wenn in der falschen Klasse

Q: Observer Design Pattern? Use Cases?
A: > Überall dort, wo 1-zu-n-Beziehungen sind, kann das Observer Design Pattern verwendet werden
> definiert die 1-zu-n-Beziehung zwischen Objekten
> hält abhängige Objekte über Zustandsänderungen auf dem Laufenden (der Observer wartet einfach bis er was bekommt)
nicht beschränkt auf User Interface Zeug mit Observer, geht überall dort wo 1-zu-n ist
Anwendungsfälle:
- eine Abstraktion mit zwei Aspekten, die voneinander abhängig sind;
Kapselung (encapsulation) in verschiedenen Objekten
Pattern verwenden, wenn → Änderung und Wiederverwendung von Aspekten trennen
- eine Zustandsänderung bewirkt weitere Zustandsänderungen (an anderer Stelle) ohne statische Kenntnis der zu ändernden Objekte
- Objekte sollen über etwas informiert werden, aber diese Objekte sind statisch nicht bekannt (lose Kopplung)

Q: Observer Design Pattern - Struktur und deren Konsequenzen?
A: Struktur:
> Subject hat eine Liste von Observern
Konsequenzen aufgrund der Struktur:
abstract Subject und abstract Observer -> für abstrakte decoupling -> abstrakte Kopplung zwischen Subject und Observer
→ das konkrete Subject muss nichts über den konkreten Observer wissen und umgekehrt
→ Subject und Observer können zu verschiedenen Schichten gehören
Übertragung (broadcast) erfolgt automatisch
→ Observer können jederzeit hinzugefügt und entfernt werden
unerwartete Aktualisierungen aufgrund fehlender Informationen möglich
→ Gründe schwer zu finden
→ Kosten für Updates kaum vorhersehbar (teils hoch)
(Problem: Wann soll die Benachrichtigungs- und Aktualisierungsnachricht gesendet werden? wie oft?)

Q: State Design Pattern - Kurzbeschreibung? Use Cases? Beispiel?
A: ermöglicht einem Objekt, sein Verhalten zu ändern, wenn sich der interne Zustand ändert; scheint die Klasse zu verändern
> allg. Zustand ändern -> immer wenn irgendein Wert einer Variable im Objekt geändert wird
> aber State Design Pattern Zustand ändern ist ein schwerwiegende Zustandsänderung bei der es dann so wirkt als würde sich die Klasse des Objekts ändern
Use Cases:
- Das Verhalten hängt vom Objektstatus ab und ändert sich zur Laufzeit entsprechend dem Status
- Um zu vermeiden, dass conditional Statements vom Objektstatus abhängen (oft durch Konstanten ausgedrückt, sehr viele ifs im Programmcode), ist jeder Zweig der conditional Statement in einer eigenen Klasse
Beispiel:
FOOP Beispiel - Maus in Subway verhält sich anders als an der Oberfläche (z.B. läuft schneller an der Oberfläche)

Q: State Design Pattern - Struktur/Aufbau?
A: Struktur:
> der "Context" kann den "State" verändern
> der "Context" enthält einen variablen Zustand ("State")
Beispiel:
der "Context" ist eine Ampel und der "State" ist eine der Farben Grün, Gelb, Rot und die "ConcreteStates" sind die Farben Rot, Grün, Gelb; die state Kante von Context ist dabei der aktuelle State also z.B. Grün
> request() sendet dann z.B. "nextState" und der State udn die ConcreteStates handlen den request

Q: Was haben wir in der Vorlesung/letztes Semester gemacht?
Zusammenfassung/Übersicht!
A: unter anderem:
> Zusicherungen
> Namen
> Generizität !!
> Patterns:
- Observer Design Pattern
- State Design Pattern
> Smalltalk
> Eiffel

Q: SOLID?
A: > SOLID ist eines der bekanntesten Designprinzipien in der objektorientierten Softwareentwicklung.
> Es ist ein Akronym für die folgenden fünf Designprinzipien:
- Single Responsibility Principle
- Open/Closed Principle (open for extensions, closed for changes)
- Liskov Substitution Principle (Objekte von subclasses verhalten sich genauso wie die Objekte von superclasses)
- Interface Segregation Principle
- Dependency Inversion (don't think about inheritance/Vererbung)
> bekannt in alten Firmen

Q: Vererbungsanomalie (inheritance)?
A: > ernsthafte Schwierigkeit, Vererbung und Parallelität mit Subtying auf einfache und zufriedenstellende Weise innerhalb einer concurrent/nebenläufigen OOP zu kombinieren
> In den meisten nebenläufigen OOPs wird die Synchronisierung durch Synchronisierungscode erreicht, der die Annahme von Nachrichten durch Objekte steuert (Synchronisationsbeschränkungen)
> Synchronisationscode ist oft schwer zu vererben und erfordert umfangreiche Neudefinitionen
> führt dazu dass geerbter Methoden neu definiert werden müssen, um die Integrität nebenläufige Objekte aufrechtzuerhalten
> Die Anomalie ist schwerwiegend, da sie die Vorteile einer Vererbung völlig zunichte machen könnte
> Synchronisationscode kann nur effektiv vererbt werden mit nicht-trivialen Klassen-neu-Definitionen
> 3er Kombination Vererbung und Nebenläufigkeit mit Subtyping gehen nicht zusammen

Q: Was war die erste OOP? Was kam danach?
A: > Algol war eine der ersten Programmiersprache überhaupt
> danach Simula als erste "OOP"
- Simula hatte Konzepte von Klassen, Objekten und Vererbung
- aber hatten andere Bedeutung z.B. Klasse heute ist eher ein Record/Struct
- dieses Konzept hat schon davor exisitert, aber Simular war erste die es Klasse genannt hat
- Simula hat co-routines entwickelt/erfunden
- in Simula gab es noch kein Subtyping und keine Abstraktion
> danach Smalltalk als erste wirklich echte OOP
- Smalltalk hat das Konzept erfunden die reale Welt in der Programmiersprache zu simulieren
> zu der Zeit bzw. vor OOP sehr viel -> Prozedurale Sprachen
- Probleme mit großem und komplexem Code
- Seiteneffekte sind wichtig
> Und danach:
- Abstraktion hinzugefügt
- durch Abstraktion hat man Kontrolle über eine große Code Basis/große Programme (objektorientiertes Denken)
- OOP gehts darum an das große Ganze zu denken, abstrakt
> C++
- gutes von Simula und Smalltalk zusammen

Q: Observer Design Pattern - Implementierungsentscheidungen und Varianten?
A: (Model View Container enthält als Teil das Observer Pattern)
> Subject als Parameter von „update“ (braucht man zur Differenzierung)
> „notify“, ausgelöst durch:
- Client des Subjects → fehleranfällig und schwieriger, Client kann vergessen, bzw. kommt nicht dazu z.B. wenn Exception geworfen wird
- Zustandsverändernde Operationen des Subjects → unnötige Aktualisierungen, aber safe-side, man verpasst keine Changes
> Das Subject ist für die Benachrichtigungsnachricht verantwortlich. Dies ist eine Speichervariante, die jedoch viel Overhead verursacht, da es viele/zu viele Aktualisierungsnachrichten gibt.
> Wenn der Client für die Benachrichtigungsnachricht verantwortlich ist, ist dies nicht gespeichert und es gibt zu wenige Aktualisierungsnachrichten
> Der Status des Subjects muss vor „notify“ konsistent sein.
> Referenzen in Observern berücksichtigen, bevor Sie ein Subject löschen
> „update“ mit vielen oder wenigen Informationen in den Parametern
Push- oder Pull-Modell -> Designentscheidung, die man treffen muss
Pull -> wenige Infos im Parameter, Subject muss weiter nachfragen
Beispiel:
Das Subject ist ein Text und der Observer ein Fenster, in dem der Text angezeigt wird. Aktualisieren mit dem gesamten Text als Parameter oder vom Observer eine Aktualisierung in einem bestimmten Teil des Textes anfordern
> Observer registrieren sich nur für einige Aspekte

Q: Konsequenzen und Implementierung des State Design Pattern?
A: Konsequenzen:
> lokalisiert zustandsspezifisches (state-specific) Verhalten
> trennt Verhalten in verschiedene States (keine ifs, aber ein anderer State)
Schwierigkeit/Nachteil:
→ gemeinsames Verhalten zu haben, dass für mehrere States gilt
→ Code über viele Klassen verteilt
Vorteil:
→ leicht erweiterbar mit Zuständen (einfach eine neue Klasse hinzufügen) und Zustandsübergängen
Wichtigte Eigenschaft: Zustandsübergänge werden explizit (nur ein einziger Variable ändern, um den State zu ändern)
→ Alle Zustände sind aufgrund atomarer Zustandsübergänge selbstkonsistent (es gibt keinen Zwischenzustand, es is entweder der neue oder der alte State)
State Objekte können von mehreren Contexts gemeinsam genutzt werden
Implementierung:
Wer verursacht/definiert State Übergänge? zwei Alternativen:
- Context: Der nachfolgende State hängt nicht vom aktuellen State ab (wenn man etwas braucht wie z.B. change to specific State x)
- State: starke Abhängigkeit zwischen Unterklassen
(Zustandsänderung im State > jeder State weiß, welcher State als nächstes nach ihm kommt)
Zustandsübergangstabelle (state transition table) als Alternative
- auf Zustandsübergänge konzentrieren, nicht auf Verhalten
- ein Array – States haben Zahlen – Index ist der aktuelle State und Wert ist der nächste State
- Zustandsübergangstabelle kann generiert werden (durch Parser oder Lexer)
- Werkzeuge zum Erstellen von diesen Tabellen aus Grammatik, z.B. Lex (nicht per Hand erstellen !)
> oft benötigen wir nur ein einzelnes Objekt pro State (Singleton)
> dynamische Vererbung als Alternative (z. B. in Self) -> wenn sich eine geerbte Klasse ändert, handelt es sich um eine Art State Design Pattern

Q: Was sind co-routines?
A: > anstatt von Parallelität
> altes Konzept, wo es noch keine Parallelität gab
> von Siluma erfunden, für Simulationen braucht man concurrent Dinge, die zur gleichen Zeit laufen (es gab keine Hardware, die Parallelität supportet hat)
> es gibt 2 Stack für Methodenaufrufe, so wie bei Nebenläufigkeit mit mehreren Threads
> man wechselt zwischen den Methoden Stacks explizit hin und her
> modernere Variante: Go-Routines von Go (co-routines mit Parallelität zusammen, um Parallelität effektiv zu nutzen)

Q: Für was gibt es Objekt-orientierte Programmierung?
A: für große und komplexe Systeme, die über lange Zeiträume gewartet werden
Abstraktes Denken ist das Schlüsselkonzept
OOP erfordert hochprofessionelle und erfahrene Entwickler - Experten
→ unzureichende personelle Ressourcen → teuer
HEUTE:
OOP nur dort einsetzen, wo es wirklich nötig ist, nur für die gesamte Architektur
→ einfachere Teile mit kostengünstigeren Techniken entwickeln (Microservices
→ Abstraktionsgrad für Systemteile reduzieren
→ vorgefertigte Frameworks und Umgebungen nutzen (Handy Apps - Entwicklung billig, weil Daten sammeln)
→ ziemlich oft: Qualitätsstandards reduzieren (insbesondere Sicherheit)

Q: von wem wird C++ verwendet? Philosophie und die Konsequenzen davon?
A: sehr wichtige Info: kommt von C, daher viele die C++ programmieren, wollen eigl C programmieren, aber C alt
Philosophie hinter C++
unterstützt objektorientierte Programmierung,
ABER zwingt Programmierer nicht dazu, teure Konzepte zu verwenden
Konsequenzen
> Referenzparameter und Wertparameter (In Java gibt es nur Referenzparameter, außer für ganze Zahlen; in C++ gibt es Wert Parameter -> Kopie wird übergeben)
> dynamische Bindung für virtuelle Memberfunktionen, aber statische Bindung für nicht-virtuelle Memberfunktionen (statische Bindung ist der standard)
> Kein garbage collection, explizite Speicher(de)zuweisung (für Echtzeitprogrammierung, Laufzeitaufwand)
→ destructors ~class() wichtig für die Bereinigung
→ virtual ~class(), wenn member Funktionen virtual sind (bei dynamischen Binden)
Man kann Frameworks einbinden, die Garbage Collection zur Verfügung stellen, aber man muss sich entscheiden -> will man Garbage Collection oder nicht
> Absichtliche Löcher im Typensystem (ungeprüfte Arraygrenzen, casts, dangling Referenzen, ...)

Q: Was sind Templates in C++?
A: > Template-Mechanismus in C++ sehr fortgeschritten
> ähnlich wie Generizität in Java, nur älter
> Template Mechanismus ist Turing vollständig (man kann damit alles berechnen)
> Konstanten als generische Parameter verwenden (T und n im Beispiel):
template<typename T, int n> class buffer { ... };
> (Teil-)Spezialisierung mit „Pattern Matching“ (manche generische Parameter weglassen, aber dafür default Werte):
template<int n> class buffer<bool, n> { ... };
template<> class buffer<bool, 0> { ... };
> Fallunterscheidungen über template welche Implementierung von buffer verwendet wird
> implizite Instanziierung (Parameter werder ersetzt), wenn keine entsprechende Spezialisierung verfügbar ist
> Code für Methoden wird nur erzeugt, wenn er verwendet wird
(anders als Generizität in Java, in Java wird nichts durchoptimiert, in C++, wenn Code nicht verwendet wird, wird er nicht erzeugt)
> inneinander verschachtelte Templates sind in C++ ganz normal, häufig verwendet, um effizient zu sein

Q: Policy-Based Programming? An einem Beispiel erklären!
A: > Programmiertechnik hinter der Standard Template Library - STL in C++
> Konzept: nichts unnötiges machen; alles was der Compiler erledigen kann, soll der Compiler machen; zur Laufzeit relativ nichts machen
> Hello World wird zu einem Template gemacht, mit einem Parameter der die Sprache ist
> HelloWorld ist ein Untertyp von Typ lang
> Typ lang kommt vom Template Parameter
> mit typedef wird neuer Typ eingeführt mit Name "MyHelloWorld"
> es muss NUR EIN Objekt erzeugt werden (EIN Objekt von HelloWorld, in anderen Programmiersprachen geht das so nicht)
> das Beispiel dient auch dazu die Mächtigkeit von Templates zu zeigen
> man kann einen Untertyp von einem generischen Parameter erzeugen, das geht fast niergends nur in C++

Q: Alternative zu Policy-Based Programming?
A: > Alternative in Java = Strategy Design Pattern
> geht aber dann nicht mit einem Objekt, sondern es müssen mindestens 2 Objekte erstellt werden, um die gleiche Funktionalität zu haben
> diese Alternative ist felxibler als Policy-Based Programming ABER weniger effizient
> muss man dynamisch machen, mit Interface und Konstruktor in HelloWorld wo man Nachricht übergeben kann

Q: Was sind Traits?
A: > kennt man unter einem anderen Namen, weil es mittlerweile in Java auch geht das man Funktionalität in ein Interface also Implementierung in einem Interface hat
> diese Interface default Implementierung ist ein Trait

Q: Was sind Lambda Ausdrücke?
A: > KURZDEFINITION: Lambda Expression sind Objekte von inneren Klassen
Lambda Expression ist eine anonyme innere Klasse, nur eine Funktion darf im Interface stehen
Methode in einem Objekt einkapselt
lokaler Kontext bleibt erhalten (im Gegensatz zum Funktionszeiger)
anonym, nur über Objekt zugänglich
GVM ist für Lambda erweitert worden, weil es können nicht so viele Klassen erstellt werden, vereinfacht, anonsten ist es eine ganz normale Klasse bzw. Objekt
einfache Syntax = syntaktischer Zucker + Intelligenz
Syntaktische Varianten:
- Parametername -> Ausdruck entspricht return
- (mehrere Parameternamen) -> Anweisung z.B. println
- () -> { mehrere Anweisungen }

Q: Warum funktioniert Typinferenz in Lamda Ausdrücken?
A: > Typinferenz funktioniert gut in funktionalen Sprachen
> in objektorientierten Programmiersprachen funktioniert es nicht, wenn man es an Stellen verwendet wo Ersetzbarkeit sein kann
> bei Lambda, gibt es keine Ersetzbarkeit (kann keinen Untertypen bilden), daher funktioniert Typinferenz´
> über die Syntax wird verhindert, dass Unertypen gebildet werden können
> daher können Typen automatisch berechnet werden (Typ über Generizität leicht berechenbar)

Q: Erkläre Streams in Java!
A: > Java 8 Streams sind einfachere Iteratoren bzw. Spliteratorn (= aufgemotzte externe Iteratoren)
create → { modifying operation → } closing operation
create = Streamerzeugende Operation (Ergebnis ist ein Stream); ein Iterator wird erzeugt
modifying operation = beliebig viele modifizierende Operationen (z.B. auch filter); ist ein Iterator; ließt Elemente von anderen Iterator
closing operation = abschließende Operation; liest vom Iterator
> die Ausführung der Operationen kommt vom Interator
WICHTIG: Lazy Evaluation
> es wird erst Element geholt, wenn es aufgerufen wird funktional, weil Reihenfolge keine Rolle spielt
> Spliterator und nicht einfacher Iterator, weil Spliterator kann auch ganzen Block von Elementen übergeben/übernehmen (relevant bei parallelen Streams)