Sie sind auf Seite 1von 38

4 Dynamische Typinformation und Statische Parametrisierung

 Statische typisierte prozedurale und funktionale Sprachen unterscheiden strikt


zwischen Typinformationen zur Übersetzungszeit und dynamischen Daten zur
Laufzeit.
 Objektorientierte Programmiersprachen benötigen dynamische Typinformation für
das dynamische Binden zur Ausführungszeit, im Gegensatz zu statisch typisierten
Sprachen.
 Java ermöglicht zur Laufzeit den direkten Zugriff auf die Klasse eines Objekts, die
Überprüfung von Objektinstanzen und die überprüfte Umwandlung des deklarierten
Typs.
 Das Kapitel behandelt den Umgang mit dynamischer Typinformation sowie statische
Formen der Parametrisierung wie Generizität, Annotationen und aspektorientierte
Programmierung.

4.1 Generizität

 Generische Klassen, Typen und Methoden in Java verwenden Typparameter.


 Generizität ist ein statischer Mechanismus ab Java Version 1.5, der keine dynamische
Bindung wie bei Untertypen erfordert.
 Diese Unterscheidung verspricht Effizienz, kann jedoch beim Programmieren
manchmal unerwartete Einschränkungen mit sich bringen.

4.1.1 Wozu Generizität?

 Verwendung von Typparametern anstelle expliziter Typen im Programm.


 Flexibilität bei der Entwicklung von Code für verschiedene Datentypen.
 Beispiel: Erstellung von Code für Listen von Zeichenketten und später für Integer-
Listen.
 Typparameter ermöglichen einfache Anpassungen des Codes ohne Kopieren.

4.1.2 Einfache Generizität in Java

 Einführung von generischen Klassen und Interfaces mit Typparametern.


 Beispiel einer generischen Liste (List<A>), die Elemente eines bestimmten Typs
speichern kann.
 Compiler erzeugt verschiedene Varianten des Codes basierend auf Typparametern.

4.1.3 Gebundene Generizität in Java

 Gebundene Typparameter ermöglichen das Festlegen von Schranken für


Typparameter.
 Beispiel: Die Klasse Scene<T extends Scalable> akzeptiert nur Typen, die das Scalable-
Interface implementieren.
 Erhöhte Sicherheit durch statisch bekannte Methoden und Variablen in den
Schranken.

Wildcards und Flexibilität


 Wildcards (wie <? extends Polygon>) bieten Flexibilität bei der Verwendung von
Generizität.
 Beispiel: drawAll(List<? extends Polygon> p) erlaubt das Zeichnen verschiedener Polygon-
Typen.
 Einschränkungen in Java: Typparameter können nicht zur direkten Erzeugung neuer
Objekte verwendet werden.

Einschränkungen in Java

 Generizität in Java hat Einschränkungen aufgrund von Kompatibilitätsbedingungen.


 Keine impliziten Untertypbeziehungen zwischen unterschiedlichen generischen
Typen.
 Einschränkungen bei der Verwendung von Arrays und Typparametern in bestimmten
Kontexten.

4.2 Verwendung von Generizität

4.2.1 Richtlinien für die Verwendung von Generizität

Generizität ist sinnvoll, wenn sie die Wartbarkeit verbessert. Entscheidungshilfen:

Gleich strukturierte Klassen und Methoden:

 Verwende Generizität bei mehreren gleich strukturierten Klassen oder Methoden, z.


B. Listen, Stacks, Suchfunktionen.
 Faustregel: Containerklassen sollen generisch sein.

Abfangen erwarteter Änderungen:

 Verwende Generizität, wenn Änderungen der Parametertypen absehbar sind.


 Faustregel: Verwende Typparameter, wenn erwartet wird, dass sich Typen in
verschiedenen Versionen ändern.

Erkennen gleicher Strukturen:

 Generizität ist sinnvoll, wenn mehrere Variablen vom gleichen, aber nicht von Anfang
an festgelegten Typ benötigt werden.
 Faustregel: Generizität ist dort sinnvoll, wo mehrere Variablen vom gleichen (nicht
von Anfang an fix festgelegten) Typ notwendig sind.

Verwendbarkeit:

 Generizität und Untertypbeziehungen sind oft austauschbar. Entscheide zwischen


ihnen basierend auf Natürlichkeit und Erfahrung.
 Generizität und Untertypbeziehungen ergänzen sich.
 Faustregel: Überlege, ob die Aufgabe besser durch Ersetzbarkeit, Generizität oder
eine Kombination gelöst wird.

Laufzeiteffizienz:
 Laufzeiteffizienz sollte nicht der primäre Faktor bei der Entscheidung zwischen
Generizität und Untertypbeziehungen sein.
 Faustregel: Lass Überlegungen zur Laufzeiteffizienz beiseite.

4.2.2 Arten der Generizität

Homogene Übersetzung (Java):

 Jede generische Klasse wird in genau eine Klasse mit JVM-Code übersetzt.
 Typparameter werden durch die Schranke ersetzt, und Typparameter ohne Schranke
durch Object.

Heterogene Übersetzung (C++):

 Jede Verwendung einer generischen Klasse erzeugt eigenen übersetzten Code.


 Bessere Laufzeiteffizienz durch separate Codeerzeugung, jedoch mit erhöhtem
Codeumfang.

Gebundene Generizität (Ada):

 Schranken werden als Funktionen angegeben, die Typen für Typparameter


bereitstellen müssen.
 Flexibilität durch individuelle Schranken für jeden Typparameter.

Generizität kann in verschiedenen Sprachen auf unterschiedliche Weisen implementiert


werden, wobei jede Methode Vor- und Nachteile hat.

4.3 Typabfragen und Typumwandlungen

Der Abschnitt 4.3 aus Ihrem Text beschäftigt sich mit dynamischer Typinformation, speziell
mit Typabfragen (instanceof-Operator) und Typumwandlungen in Java. Hier sind die
wichtigsten Punkte und Erklärungen:

4.3.1 Verwendung dynamischer Typinformation

 Jedes Objekt in Java hat eine Methode getClass(), die ein Objekt vom Typ Class
zurückgibt, welches die interne Repräsentation der Klasse des Objekts darstellt.
 Es gibt ein Class-Objekt für jedes Interface, jede Klasse, jeden elementaren Typ sowie
davon abgeleitete Array-Typen.
 Man kann Class-Objekte direkt ansprechen, z. B. int.class, Person.class, usw.

instanceof-Operator

 Der instanceof-Operator wird verwendet, um festzustellen, ob das dynamische Objekt


einer Referenz ein Untertyp eines bestimmten Typs ist.

 Hier wird überprüft, ob p eine Instanz von Student ist.

Explizite Typumwandlungen (Casts)

 Dynamische Typabfragen allein reichen nicht aus, um auf Methoden und Variablen
des dynamischen Typs zuzugreifen.
 Explizite Typumwandlungen (Casts) werden verwendet, um den deklarierten Typ
einer Referenz zur Laufzeit zu ändern.

 Hier wird eine Typumwandlung (Point3D) p durchgeführt, um auf die zusätzliche


Variable z zuzugreifen.

Wartbarkeit und Gefahren

 Dynamische Typabfragen und Typumwandlungen sind mächtige Werkzeuge, können


aber zu Wartbarkeitsproblemen führen.
 Falsche Verwendung kann Fehler verdecken und die Wartbarkeit erschweren.
 Typumwandlungen sind nur durchführbar, wenn der deklarierte Typ zur Laufzeit den
gewünschten Typ hat oder gleich null ist.

Faustregel

 Typabfragen und Typumwandlungen sollten nach Möglichkeit vermieden werden.


 In einigen Fällen sind sie notwendig und angebracht, aber ihre Verwendung sollte
sparsam sein.

Diese Prinzipien dienen dazu, die dynamische Typinformation und Typumwandlungen in Java
effektiv und sicher zu nutzen.

4.3.2 Typumwandlungen und Generizität: Zusammenfassung

Homogene Übersetzung generischer Klassen und Methoden


 Spitze Klammern werden entfernt, Typparameter durch Object oder die erste
Schranke ersetzt.
 Automatische Casts auf Object werden im Code eingefügt und später vom Compiler
optimiert.

Beispiel: Liste ohne Generizität

 Listenklasse und Interfaces aus Abschnitt 4.1.2 als Beispiel für homogene
Übersetzung.
 Automatische Casts auf Object in Konstruktoren und Methoden, sinnlos und
optimierbar.

Typumwandlungen in der Praxis

 Beispielklasse ListTest zeigt Typumwandlungen in Methodenaufrufen.


 Ziel: Auswahl der richtigen Methode bei Überladung durch Typumwandlungen.

Vorteile der Generizität

 Bessere Lesbarkeit und höhere Typsicherheit im generischen Code.


 Beispiel mit Fehlermeldung im generischen Code im Vergleich zu Laufzeitfehler ohne
Generizität.

Faustregeln für Typumwandlungen

 Nur sichere Formen der Typumwandlung verwenden, die keine Ausnahmen auslösen.
 Bei dynamischen Typabfragen sicherstellen, dass Annahmen im alternativen Zweig
durch Zusicherungen abgesichert sind.
 Verwendung von Generizität vor unsicheren Formen von Typumwandlungen.
 Bei Generizität darauf achten, dass alle spitzen Klammern korrekt gesetzt sind.

Umgang mit Einschränkungen der Generizität

 Gemischte Verwendung von generischen und homogen übersetzten Klassen möglich.


 Generics vor dynamischen Typumwandlungen bevorzugen.
 Bei dynamischen Typabfragen und Typumwandlungen auf sichere Formen achten.
 Fehlerhafte Typumwandlungen können Laufzeitausnahmen verursachen, deshalb
Generizität bevorzugen.

Generizität und Raw-Types in Java

 Beispiel zeigt die Verwendung von generischen Klassen und Raw-Types in der
Methode equals.
 Raw-Types entstehen durch homogene Übersetzung, Type-Erasure entfernt
Typparameter.
 Wichtig: Spitze Klammern bei Typdeklarationen korrekt setzen, Generizität
bevorzugen.

4.3.3 Kovariante Probleme

Kovariante Eingangsparametertypen

 Eingangsparameter können nur kontravariant sein (Prinzip der Ersetzbarkeit).

Kovariante Probleme und Lösungen

 Manchmal wünschen wir uns jedoch kovariante Eingangsparametertypen.


 Lösung: Dynamische Typabfragen und Typumwandlungen.
Beispiel: Futter für Tiere

 Tiere (Animal), Futter (Food), Grass und Meat als Untertypen von Food.
 Natürliche Erwartung: Cow isst Grass, Tiger isst Meat.
 Schwierigkeit: Typsicherheit nicht direkt darstellbar.

Lösungsansatz mit dynamischen Typabfragen

 Verwendung von instanceof in den eat-Methoden.


 Dynamische Typabfragen notwendig, auch wenn Aufrufer die richtige Futterart
kennt.

Überladen von Methoden als Lösung

 Überladen von eat in Cow und Tiger mit spezifischeren Parametertypen (Grass und
Meat).
 Reduziert die Notwendigkeit von dynamischen Typabfragen.

Umschreiben des Programms

 Möglichkeit, Typabfragen und Typumwandlungen zu vermeiden.


 Unerwünschte Aktionen bei kovarianten Problemen bleiben erhalten.
 Kovariante Probleme sollten idealerweise vermieden werden.

Faustregel

 Kovariante Probleme sind möglichst zu vermeiden.

Binäre Methoden

 Binäre Methoden haben mindestens einen formalen Parameter, dessen Typ gleich
der enthaltenden Klasse ist.
 Beispiel: equal-Methode in Point2D und Point3D.
 Verwendung einer abstrakten Oberklasse Point und uncheckedEqual-Methode für den
eigentlichen Vergleich.

Fazit

 Dynamische Typabfragen und Typumwandlungen können kovariante Probleme lösen,


aber Überladen von Methoden bietet eine bessere Lösung in vielen Fällen.

4.4 Überladene Methoden und Multimethoden


Dynamisches Binden und Überladen

 Dynamisches Binden in Java erfolgt über den dynamischen Typ eines Parameters.
 Die auszuführende Methode wird durch den dynamischen Typ des Empfängers (z.B.,
x in x.equal(y)) festgelegt.

Multimethoden

 Im Allgemeinen (nicht in Java) kann das dynamische Binden auch den dynamischen
Typ des Parameters einschließen.
 Unterschied zwischen Überladen und Multimethoden.

Deklarierte versus dynamische Argumenttypen

 Beispiel zu kovarianten Problemen (Abschnitt 4.3.3) zur Verdeutlichung.


 Bei Überladen ist der deklarierte Typ des Arguments entscheidend, nicht der
dynamische.
 Komplexität und Verwechslung von dynamischen und deklarierten Typen bei der
Methodenauswahl.

Faustregel für Überladen

 Überladen so verwenden, dass es keine Rolle spielt, ob bei der Methodenauswahl


deklarierte oder dynamische Typen der Argumente verwendet werden.

Unterscheidung zwischen Überladen und Überschreiben

 Fehler bei unbewusster Verwendung von Überladen statt Überschreiben.


 Verwendung der @Override-Annotation zur Absicherung.
 Kovarianz nur für Ergebnistypen erlaubt (ab Java 1.5).

Komplexität von Multimethoden

 Multimethoden erfordern Berücksichtigung dynamischer Typen aller Argumente.


 Mehrdeutigkeiten bei Methodenauswahl und mögliche Lösungsansätze.
 Frage nach der Gesamtbetrachtung: Sind Multimethoden günstiger als überladene
Methoden? Offen für Diskussion.

Fehlerquellen in Java

 Fehler bei unbeabsichtigter Verwendung von Überladen statt Überschreiben.


 Notwendige Achtsamkeit und Verwendung von @Override.
 Kovarianz nur für Ergebnistypen erlaubt (ab Java 1.5).
4.4.2 Simulation von Multimethoden

Mehrfaches dynamisches Binden

 Multimethoden nutzen mehrfaches dynamisches Binden für die Auswahl von


Methoden.
 In Java erfolgt dynamisches Binden nur einfach; mehrfaches Binden wird simuliert.

Beispielumsetzung

 Beispiel mit Tieren und Futterarten aus Abschnitt 4.3.3.


 Ersetzen von dynamischen Typabfragen und Typumwandlungen durch mehrfaches
dynamisches Binden.
 Verwendung von Visitor-Pattern als Lösung.

Automatische Umwandlung

 Umformung von Lösungen mit dynamischen Typabfragen oder Multimethoden.


 Ersetzen von if-Anweisungen durch mehrfaches dynamisches Binden.
 Strukturverbesserung des Programms durch Entfernen von dynamischen
Typabfragen.

Nachteil des Visitor-Patterns

 Anzahl benötigter Methoden im Visitor-Pattern kann schnell groß werden.


 Formel für die Berechnung der Methodenanzahl bei k dynamischen Bindungen und ni
Möglichkeiten.
 Kreativitätsansätze zur Reduzierung der Methodenzahl, wie die Bildung von Klassen
für Gruppen mit gemeinsamen Eigenschaften.

Alternativen und Optimierungen

 Reduktion der Klassenanzahl durch Bildung von Gruppen mit gemeinsamen


Eigenschaften.
 Beschreibung der Rolle eines Objekts zur einfachen Identifikation erlaubter Aktionen.
 Weiterleitung des verfügbaren Futters an eine Methode in einem Objekt zur
Vergleich mit erlaubter Futterart.
 Fokus auf Reduktion der Klassenanzahl und einfache Beschreibung von Objekten zur
Verbesserung der Struktur.

4.5 Annotationen und Reflexion

4.5.1 Annotationen und Reflexion in Java

Idee hinter Annotationen

 Annotationen markieren Programmteile für spezielle Behandlung durch


Laufzeitsystem oder Entwicklungswerkzeuge.
 Ohne explizite Überprüfung haben Annotationen keine Auswirkungen auf die
Programmsemantik.

Umsetzung in Java (4.5.1)

 Annotationen in Java starten mit dem Zeichen "@", z.B., @Override vor
Methodendefinitionen.
 Eigene Annotationen können mit Argumenten versehen werden und müssen vor
Verwendung deklariert werden.
 Beispiel: @BugFix-Annotation mit Datenfeldern für Bug-Fix-Informationen.

Definition von Annotationen

 Syntax ähnlich Interface-Definitionen, aber mit spezifischen Regeln für erlaubte


Datentypen.
 Verwendung von @Retention und @Target zur Festlegung von Sichtbarkeit und
Anwendungsbereich.
 Weitere Annotationen wie @Documented und @Inherited für Dokumentation und
Vererbung von Annotationen.
 Default-Werte für Annotationen ermöglichen optionale Parameter.

Verwendung zur Laufzeit (Reflexion)

 Annotationen mit @Retention(RUNTIME) können zur Laufzeit abgefragt werden.


 Compiler generiert Interface für Annotationen, das von Annotation erbt.
 Reflexion ermöglicht Zugriff auf Annotationen und andere Details zur Laufzeit.
 Beispiel: Zugriff auf BugFix-Annotation in der Klasse Buggy.

Reflexion in der Programmierung

 Reflexion erlaubt den flexiblen Zugriff auf viele Details eines Programms zur Laufzeit.
 Klassen, Methoden, Felder usw. können dynamisch analysiert werden.
 Gefahr der Reflexion: Unkontrollierte Nutzung kann zu undurchschaubarem und
schwer wartbarem Code führen.

Zusammenfassung

 Annotationen sind Markierungen für spezielle Behandlung.


 Syntax in Java mit dem Zeichen "@".
 Eigene Annotationen können mit Argumenten definiert werden.
 Reflexion ermöglicht den Zugriff auf Annotationen und andere Details zur Laufzeit.
 Gefahr der Reflexion: Vorsichtige und kontrollierte Anwendung erforderlich.
4.5.2 Anwendungen von Annotationen und Reflexion

Übliche Annotationen

 Zweck der Annotationen: Beitrag zur Programmierung ohne Syntaxänderungen.


 Beispiele:
o @Override: Kennzeichnet Methoden, die eine Methode in der Oberklasse
überschreiben.
o @Deprecated: Markiert nicht mehr zu verwendende Programmteile, erzeugt
Warnungen.
o @SuppressWarnings: Unterdrückt Compiler-Warnungen (risikoreich, Vorsicht
geboten).

@FunctionalInterface

 Einführung in Java 8.
 Verwendung: Kennzeichnet Interfaces mit genau einer abstrakten Methode.
 Erlaubt Einsatz als Lambda-Ausdrücke.
 Beispiel:

Reflexion

 Definition: Variante der Metaprogrammierung, ermöglicht Zugriff auf


Programmstrukturen zur Laufzeit.
 Gefahren: Unbekanntes Verhalten von reflektierten Methoden, Wartungsprobleme.
 Beispiel: Dynamischer Methodenaufruf mit Reflexion.

JavaBeans-Komponenten

 JavaBeans-Konzept: Einfache Klassen für den Bau grafischer Benutzeroberflächen.


 Properties: Getter und Setter für externe Zugriffe.
 Reflexion in JavaBeans: Werkzeuge nutzen Reflexion zur Erkennung von Properties
und Events.
 Diskussion: Kontroverse über den Einsatz von Gettern und Settern im
objektorientierten Stil.

@ConstructorProperties

 JavaBeans-spezifisch: Annotation für seltene Fälle, in denen Reflexion nicht


ausreicht.
 Verwendung: Zählt Konstruktorparameter auf, um Zuordnung zu Properties zu
erleichtern.
Java EE (Enterprise Edition)

 Professionelle Anwendungen: Verwendung von Enterprise JavaBeans (EJB).


 Annotationen in Java EE: Über 30 verschiedene Annotationen für Konfiguration und
Verhalten.
 Beispiel: @TransactionAttribute, @Stateful, @Stateless für Transaktionen und
Zustandsverwaltung.

Fazit

 Annotationen und Reflexion: Mächtige Werkzeuge, erfordern jedoch bewussten und


kontrollierten Einsatz.
 JavaBeans vs. Java EE: Unterschiede im Einsatz und Designprinzipien je nach
Anwendungsbereich.

4.6 Aspektorientierte Programmierung

4.6.1 Konzeptuelle Sichtweise

 Betrachtungsebenen:
o Konzeptuelle Sichtweise: Hohe Ebene, Einbettung in das objektorientierte
Paradigma.
o Implementierungsebene: Niedrige Ebene, Eingriff in das Objektgefüge.

 Einflussfaktoren auf Berechnungsergebnisse:


o Das Programm selbst.
o Die Semantik der verwendeten Sprache.
o Die auf das Programm angewandten Daten.
 Veränderungsmöglichkeiten:
o Änderung des Programms.
o Anpassung der Daten.
o Anpassung der Semantik der Sprache.
 Aspektorientierte Programmierung:
o Veränderung der Sprachsemantik, um Ergebnisse zu beeinflussen.
o Ziel: Ergebnisänderungen ohne direkte Änderungen am Programmtext oder
an den Daten.
 Graubereich:
o Verwendung von Metaprogrammierung und Precompilation als Hilfsmittel.
o Ziel: Reduzierung von Risiken und Kontrolle über Mächtigkeit.

Konzeptuelle Sichtweise: Beispiel

 Ziel:
o Entwicklung eines Authentifizierungspakets für Bankensoftware.
o Überprüfung von Zugriffsrechten ohne direkte Änderungen an bestehendem
Code.
 Herangehensweise:
o Identifikation kritischer Stellen (z. B., Methodenaufrufe, Datenbankzugriffe).
o Festlegung von Kriterien (Klassen, Namenskonventionen) zur Identifikation.
o Extraktion von Kontextinformationen (z. B., Zugriffsrechte, Daten).
 Umsetzung:
o Parametrisierung durch Hinzufügen von zusätzlichem Code an identifizierten
Stellen.
o Abhängigkeit von Annahmen und Einhaltung von Projektregeln.

Aspektorientierte Terminologie

 Separation-of-Concerns:
o Trennung von Belangen in der Programmierung.
o Core-Concerns: Kernfunktionalitäten, hoher Klassenzusammenhalt.
o Cross-Cutting-Concerns: Querschnittsfunktionalitäten, schwerer
Klassenzusammenhalt.
 Parametrisierung:
o Lücken im Programm, die später befüllt werden.
o Abhängigkeit zwischen Lücken und Befüllung.
 Einfluss der Annahmen:
o Identifikation von Programmstellen und Extraktion von Kontextinformationen.
o Wahrung von Einhaltung projektspezifischer Regeln und Konventionen.

Integration der aspektorientierten Programmierung

 Ergänzung zur objektorientierten Programmierung:


o Beitrag in speziellen Situationen (Querschnittsfunktionalität).
o Wertvolle Ergänzung, aber nicht für alle Fälle geeignet.
o Kernfunktionalitäten als Normalfall, Querschnittsfunktionalitäten eher selten.
 Beispiele für Querschnittsfunktionalitäten:
o Überprüfung von Zugriffsrechten.
o Generierung von Debug-Informationen oder Log-Dateien.

4.6.2 AspectJ

 Überblick:
o AspectJ ist ein Werkzeug für die aspektorientierte Programmierung in Java.
o Es arbeitet mit Java-Programmen, die aus mehreren Klassen bestehen.
o Zusätzliche Dateien mit der Erweiterung .aj erfassen gewünschte Änderungen
in der Java-Semantik.
o Der AspectJ-Compiler (ajc) wird für die Übersetzung verwendet, wobei Java-
Klassen, .aj-Dateien und die Bibliothek aspectjrt.jar gleichzeitig behandelt
werden.
 Wichtige Konzepte:
o Join-Point: Identifizierbare Laufzeitpunkte im Programm, wie
Methodenaufrufe oder Objektzugriffe.
o Pointcut: Ein syntaktisches Element in einer .aj-Datei, das Join-Points
auswählt und kontextspezifische Informationen sammelt.
o Advice: Spezifiziert den auszuführenden Programmcode an einem Join-Point
(vor, nach oder rund um).
 Pointcuts:
o Definieren eine Menge von Join-Points mit Syntax wie call(*
javax..*.add*Listener(EventListener+)).
o Operatoren wie ! (Negation), || (Vereinigung) und && (Schnittmenge) können
Pointcuts kombinieren.
 Pointcut-Typen (Beispiele):
o call(MethodSignature), execution(ConstructorSignature), get(FieldSignature), usw.
o Sichtbarkeitsbasiert: within(Typepattern), withincode(Method/ConstructorSignature).
o Kontrollflussbasiert: cflow(Pointcut), cflowbelow(Pointcut).
 Advices:
o Werden an bestimmten Join-Points ausgeführt.
o Typen: before(), after(), after() returning, after() throwing, around().
 Beispiel-Advices:

java
 before() : call(* Account.*(..)) { checkUser(); }
before(Connection connection) : connectionOperation(connection)
{ System.out.println("Operation auf " + connection); }

Aspects:

 Kombinieren Pointcuts, Advices, Variablen und Methodendefinitionen.


 Werden ähnlich wie Klassen definiert, jedoch mit aspect statt class.

 Beispiel-Aspect:

java
 public aspect JoinPointTraceAspect {
private int callDepth = -1;
pointcut tracePoints(): !within(JoinPointTraceAspect);
before() : tracePoints() { callDepth++; print("Before", thisJoinPoint); }
after() : tracePoints() { callDepth--; print("After", thisJoinPoint); }
private void print(String prefix, Object message) { /* Implementierung */
}
}

Ausgabe-Trace-Aspect: Ein Beispiel-Aspect (JoinPointTraceAspect), das Join-Points mit


Einrückung entsprechend ihrer Tiefe ausgibt.

5. Applikative Programmierung und Parallelausführung

5.1 Lambdas und Java-8-Streams

Hintergrund:

 Applikative Programmierung spät in Java integriert, aber schnell akzeptiert.


 Funktionale und applikative Programmierung als Basis für nebenläufige und parallele
Programmierung.
 Trotz Paradigmenunterschieden zunehmende Integration aufgrund von Big-Data-
Anwendungen und parallelen Recheneinheiten.

5.1.1 Anonyme innere Klassen und Lambdas:


 Lambdas ähnlich anonymen inneren Klassen, aber eigenständige Konstrukte.
 Anonyme innere Klassen als Syntaxvereinfachung für Lambdas.
 Lambdas sind objektbasiert, können aber leicht veränderliche Variablen aus der
Umgebung nutzen.
 Rückgriff auf die java.util.function-Paket-Interfaces für Lambdas.

Beispiele für Lambdas:

 Consumer<String> p = s -> System.out.println(s);


 Function<Integer, String> value = i -> "value = " + i;
 BiFunction<String, Boolean, String> opt = (s, b) -> b ? s : "";

Eigenschaften und Einschränkungen von Lambdas:

 Lambdas als objektbasierte Konstrukte mit definierter Schnittstelle.


 Unveränderliche Variablen im Lambda-Rumpf.
 Unterschiede zu anonymen inneren Klassen.

Syntaktische Varianten von Lambdas:

 Klassenname::Methodenname für Methoden in einer Klasse.


 Gute Lesbarkeit als Hauptgrund für diese Form von Lambdas.

Bedeutung von Lambda-Mächtigkeit in Java:

 Mächtigkeit durch Sprachelemente wie rekursive Methodenaufrufe und Schleifen


bestimmt.
 Lambdas als vereinfachte, objektbasierte Konstrukte für höhere Abstraktionsebenen.

Zusammenfassung:

 Lambdas in Java als flexible und kompakte Konstrukte für funktionale


Programmierung.
 Integration in Java-8-Streams für effiziente Datenverarbeitung.
 Anwendungsbereiche in nebenläufiger und paralleler Programmierung.

5.1.2 Java-8-Streams

Java 8 Streams als Iteratoren:

 Java 8 Streams werden als spezielles Konzept in Java 8 betrachtet und als eine Form
von Iteratoren angesehen.
 Externe Iteratoren in der objektorientierten Programmierung werden durch Streams
ersetzt, um den funktionalen Programmierstil zu unterstützen.

Interne Iteratoren in der funktionalen Programmierung:

 Funktionale Programme verwenden interne Iteratoren, die durch Rekursion über


Elemente iterieren.
 Interne Iteratoren sind Funktionen höherer Ordnung, die eine Funktion als Parameter
akzeptieren.

Arten von Stream-Operationen:

 Java 8 Streams bieten stream-erzeugende, stream-modifizierende und stream-


abschließende Operationen.
 Beispiele umfassen map, filter, reduce, collect, forEach, allMatch, anyMatch und count.

Lazy-Evaluation in Streams:

 Stream-Operationen verwenden Lazy-Evaluation, wodurch Berechnungen erst bei


Bedarf durchgeführt werden.
 Lazy-Evaluation ermöglicht die effiziente Verarbeitung von Streams.

Erweiterung von Streams mit Spliterators:

 Entwickler können die Fähigkeiten von Streams durch das Erstellen neuer
Spliteratoren erweitern.
 Spliteratoren sind eine erweiterte Form von Iteratoren, die parallele Verarbeitung
unterstützen.

Beispiel: Faktorielle Berechnung mit Streams:

 Zeigt die Verwendung von Streams für die Faktorielle-Berechnung mithilfe von
LongStream.rangeClosed und reduce.

Beispiel: Zuordnung von gemeinsam verkauften Produkten:

 Veranschaulicht die Zuordnung von gemeinsam verkauften Produkten durch


Anwendung von flatMap, groupingBy und counting.

Vergleich mit nicht-streambasiertem Ansatz:

 Bietet eine nicht-streambasierte Alternative unter Verwendung von forEach-


Methoden und Eager-Evaluation für die gleiche Zuordnungsaufgabe.

Stream-Programmierung vs. Traditioneller Ansatz:

 Betont, dass Streams eine zusätzliche Abstraktionsebene bieten, um komplexe


Aufgaben effizienter zu lösen.
 Der Fokus liegt auf der Vereinfachung und Ausdruckskraft der streambasierten
Programmierung im Vergleich zu traditionellen Ansätzen.

5.1.3 Applikative Programmierung in der Praxis

Grundlegende Erfahrungen:
Map-Reduce-Schema:

 Die Abarbeitung folgt einem fixen Schema namens Map-Reduce.


 Viele Programmieraufgaben sind nach diesem Schema lösbar.

Anwendbarkeit von Streams:

 Viele Aufgaben lassen sich gut mit Java-8-Streams lösen.


 Es bedarf einer überschaubaren Menge vorgefertigter Funktionen höherer Ordnung
und einfacher Lambdas.

Generizität spielt eine Rolle:

 Typparameter und Typen werden in der Regel über Typinferenz ermittelt.


 Typkonsistenz ist ein guter Indikator für die Korrektheit von Ausdrücken.

Weitere Erkenntnisse:

Zusicherungen und Kommentare:

 Kommentare sollen Ideen erklären, aber Zusicherungen auf kleinen Hilfsmethoden


(Lambdas) sind zu vermeiden.

Variablenverwendung:

 Im Umfeld applikativer Programmteile sollten Variablen so verwendet werden, als ob sie


final wären.
 Destruktive Zuweisungen sollten vermieden werden.

Denkweise:

 Es ist vorteilhaft, entweder in einer funktionalen oder in einer prozedural-


objektorientierten Denkweise zu bleiben.

Funktionsdesign:

 Funktionen (höherer Ordnung) sollen so allgemein wie möglich sein und


Zustandsänderungen lokal halten.

5.2.2 Funktionale Elemente in Java

Currying in Java

 Funktionen mit einem Parameter ausreichend für beliebig viele Parameter.


 Currying: Eine Funktion mit einem Parameter, die eine Funktion zurückgibt, die den
nächsten Parameter erwartet.
 Vorteile: Flexibilität bei der Auswertung, aber leicht erhöhter Aufwand und
Ressourcenverbrauch.
Currying Beispiel in Java

 Beispiel mit BiFunction und Function für zwei gleiche Funktionen.


 Syntax von Lambdas für Currying ist natürlich und erfordert keine zusätzliche
Klammerung.
 Typen von Lambdas können bei Currying komplex werden.

Pattern-Matching in funktionalen Sprachen

 Pattern-Matching: Werte in Parametern bestimmen die auszuführende Funktion.


 Beispiel für die Berechnung der Länge einer Zeichenkette mit Pattern-Matching.
 Datenstruktur in funktionalen Sprachen ist offen sichtbar, ermöglicht direkten Zugriff auf
Daten.

Herausforderung in Java: Datenabstraktion

 Java und objektorientierte Sprachen bevorzugen Datenabstraktion, was Pattern-


Matching erschwert.
 Beispielübersetzung eines Pattern-Matching-Beispiels in Java mit ineffizienter Lösung.
 Dynamisches Binden könnte das Problem lösen, erfordert jedoch weitergehende
Überlegungen.

Fazit zu Currying und Pattern-Matching in Java

 Currying bietet Flexibilität, erfordert jedoch gewisse Gewöhnung.


 In Java fehlt eine dedizierte Syntax für Pattern-Matching aufgrund von Datenabstraktion
und Sichtbarkeit von Daten.
 Eine abgespeckte Variante mit Literalen wäre denkbar, aber könnte Effizienzprobleme
aufweisen.

5.3 Nebenläufige Programmierung in Java

5.3.1 Thread-Erzeugung und Synchronisation

 Beispiel mit der Klasse Counter zeigt ein Synchronisationsproblem.


 Inkonsistenzen bei nicht synchronisiertem Zugriff auf gemeinsame Variablen.
 Verwendung von synchronized-Methoden oder -Blöcken zur Synchronisation.
 Faustregeln: Alle Methoden mit Zugriff auf Objekt- oder Klassenvariablen sollen
synchronized sein.
 Kurze Laufzeit für synchronized-Methoden empfohlen.
 Einsatz von synchronized-Blöcken für feinere Kontrolle über Synchronisation.
 Java verwendet Locking für Synchronisation.
 Einzelne Schreib- und Lesezugriffe auf volatile Variablen sind atomar.
 Verwendung von Klassen wie AtomicInteger für atomare Operationen.

Bedingte Ausführung von Threads


 Beispiel mit PrinterDriver zeigt bedingte Ausführung von Threads.
 wait() und notifyAll() für bedingte Ausführung und Weckung von Threads.
 Einsatz von Endlosschleifen mit Schleifenüberprüfung und Abfangen von
InterruptedException.
 Verwendung von Producer-Klasse als Beispiel für erzeugte Threads.

Thread-Erzeugung mit dem Runnable-Interface

 Das Runnable-Interface spezifiziert nur die run-Methode.


 Verwendung von Klassen, die Runnable implementieren, zur Thread-Erzeugung.
 Thread-Objekte und deren start()-Methode für Thread-Ausführung.
 Kontrollmöglichkeiten für die Ausführung von Threads, z.B., Abbrechen oder
Unterbrechen.
 Beachtung von veralteten Methoden und Verwendung aktuellerer Alternativen.

5.3.2 Nebenläufigkeit in der Praxis

Vorgefertigte Lösungen

 Grundlegende Sprachkonzepte der nebenläufigen Programmierung werden selten


genutzt.
 Java-Pakete java.util.concurrent und java.util.concurrent.atomic bieten vorgefertigte
Lösungen.
 Lösungen nutzen Hardwareunterstützung für Synchronisation.
 Verwendung von Klassen wie FutureTask und Interface Future für
Hintergrundberechnungen.

Aufgaben und Threads

 Konzept von Futures ermöglicht Hintergrundberechnungen und Synchronisation.


 Einsatz von Executors, insbesondere ThreadPoolExecutor, für effiziente
Aufgabenaufteilung.
 Java-8-Streams bieten einfache Handhabung großer Datenmengen mit
Nebenläufigkeit.

Thread-sichere Datenstrukturen

 Java-Paket java.util.concurrent bietet synchronisierte Varianten üblicher


Datenstrukturen.
 Klassen wie ConcurrentHashMap ermöglichen effiziente nebenläufige Zugriffe.
 Unterschiede zu synchronisierten Varianten in java.util.

Vorgehensweise

 Zerlegung von Aufgaben in Teilaufgaben, die möglichst unabhängig sind.


 Verwendung von parallelen Streams und Executors bei unabhängigen Teilaufgaben.
 Thread-sichere Datenstrukturen wie ConcurrentHashMap bei gemeinsamen Daten.
 Herausforderungen bei Abhängigkeiten und Synchronisation.
 Möglichkeit des sequentiellen Lösens bei zu hoher Komplexität.
 Faustregel: Synchronisation muss einfach gehalten werden. Verzicht auf
Nebenläufigkeit, wenn zu aufwändig.
 Bewusster Einsatz von Nebenläufigkeit in unabhängigen Bereichen.

Zusammenfassung

 Prinzipielle Schwierigkeiten und vorgefertigte Lösungen für nebenläufige


Programmierung.
 Effiziente Implementierungen, Konzepte wie Futures, Executor, Java-8-Streams, und
thread-sichere Datenstrukturen.
 Vorgehensweise bei der Aufgabenzerteilung und Betonung der Einfachheit bei der
Synchronisation.

5.3.3 Synchronisation und die objektorientierte Sicht

Umgang mit Synchronisationsproblemen

 Inkonsistenzen in der Synchronisation von Java-Klassen erfordern genaue Kenntnis


der verwendeten Klassen.
 Zu viel Synchronisation führt zu negativen Auswirkungen wie Verhinderung
gleichzeitiger Ausführung.
 Liveness-Probleme wie Deadlocks, Livelocks und Starvation sind zu befürchten.
 Lineare Anordnung zur Vermeidung von Deadlocks, aber einschränkend in der Praxis.
 Formaler Beweis von Programmeigenschaften durch Model-Checking-Tools, jedoch
begrenzte Anwendbarkeit.
 Testen bleibt trotz Herausforderungen unverzichtbar.
 Vorgefertigte Lösungen bevorzugen, da auf bewährten Techniken basierend und
intensiv getestet.

Objektorientierte Sicht

 Schwierigkeiten bei der objektorientierten Programmierung mit Nebenläufigkeit.


 Monitor-Konzept als Basis für Nebenläufigkeit in Java, wenig Unterstützung für
objektorientierte Techniken.
 Verletzung des Ersetzbarkeitsprinzips bei Synchronisation in Untertypen vermeiden.
 Schwierigkeiten bei der Beschreibung von Synchronisation in Schnittstellen.
 Empfehlung: Lokale und einfache Synchronisation, Verzicht auf Ersetzbarkeit bei
komplexer Synchronisation.
 Abhängigkeiten durch Synchronisation beeinträchtigen oft die Vererbung.
 Herausforderungen bei der Faktorisierung von Software nach objektorientierten und
nebenläufigen Gesichtspunkten.

Zusammenfassung

 Kenntnis der Synchronisationsprinzipien und -probleme in Java-Klassen ist essenziell.


 Herausforderungen bei Liveness-Problemen und Testen.
 Empfehlung für bewährte vorgefertigte Lösungen, trotz möglicher Probleme.
 Schwierigkeiten bei der Verbindung von objektorientierter Programmierung und
Nebenläufigkeit.
 Betonung der einfachen und lokalen Synchronisation, Vermeidung von übermäßiger
Abhängigkeit und Komplexität.

5.4 Prozesse und Interprozesskommunikation

5.4.1 Erzeugen von Prozessen in einer Shell

Prozesserzeugung in Linux:

 Linux-Prozesse werden vom Betriebssystem verwaltet.


 Prozesserzeugung und Interprozesskommunikation sind betriebssystemabhängig, Bezug
auf Linux.
 Betriebssysteme wie Linux haben Wurzeln in Unix, wodurch Unterschiede begrenzt sind.

Starten von Programmen in der Shell:

 Unter Linux wird oft mit einer Shell in einem Terminal-Fenster gearbeitet.
 Das Starten von Programmen erfolgt durch Eingabe des Programmnamens in die Shell.
 Die Shell sucht nach der ausführbaren Datei im Verzeichnis und startet einen neuen
Prozess.

Kommandozeilenargumente:

 Programmname oder Dateipfad, gefolgt von Kommandozeilenargumenten.


 Argumente werden durch Leerzeichen oder Tab getrennt.
 Beispiel mit Java: java Test arg1 arg2.

Dateien und Pipelines aus Java-Sicht

Standard-Ein- und Ausgabekanäle:

 Jeder Prozess hat automatisch Standardeingabe, Standardausgabe und


Fehlerausgabe.
 In Java: System.in, System.out, System.err.

Umleiten von Ein- und Ausgaben:

 Verwendung von Symbolen und Operatoren in der Shell.


 Beispiele: >, >>, <, &>, &>>.

Pipelines:

 Mehrere Prozesse können durch Pipelines verknüpft werden.


 Beispiel: cat file | wc.

Hintergrundprozesse:

 Hinzufügen von & am Ende startet einen Hintergrundprozess.


 Shell wartet nicht auf die Beendigung und ist sofort für weitere Befehle bereit.
Verknüpfen von Prozessen:

 Verwendung von ;, &&, || für die Ausführungssteuerung.

Shell-Skripte und Erweiterungen

Shell als vollständige Programmiersprache:

 Shell kennt Variablen, vordefinierte Befehle, Kontrollstrukturen, definierbare


Funktionen.

Beispiel: Shell-Skripte:

 Schreiben von Shell-Skripten, die als Textdateien gespeichert und ausgeführt werden
können.
 Nutzung von Variablen, Bedingungen (if ... else), Schleifen (for).

Erweiterungen von Pfadnamen:

 Nutzung von Pfadnamen-Erweiterungen mit Zeichen wie *, ?, [abc].


 Hochkomma-Zeichen zum Schutz vor ungewollter Expansion.

Beispiele für Shell-Befehle:

 Verwendung von echo, test, if ... then ... else ... fi, for ... do ... done.

Export von Variablen:

 Exportieren von Variablen für Beeinflussung übergeordneter Shells.

Shell als Interpreter für Skripte:

 Shell kann Skripte interpretieren, die als ausführbar markiert sind.

Zusammenfassung

 Die Shell bietet umfangreiche Möglichkeiten zur Prozesserzeugung und -


kommunikation.
 Shell-Skripte erweitern die Funktionalität und ermöglichen automatisierte Abläufe.
 Die Verwendung von Variablen, Bedingungen und Schleifen macht die Shell zu einer
leistungsfähigen Programmiersprache.

5.4.2 Umgang mit Dateien und I/O-Strömen in Java

Kommandozeilenargumente und Standard-Ein-/Ausgabe

 Die main-Methode eines Java-Programms erhält ein Array von Zeichenketten als
Kommandozeilenargumente.
 System.in ermöglicht die Eingabe von der Standardeingabe, während System.out und
System.err für die Standardausgabe und Fehlerausgabe stehen.

Dateiverarbeitung

 Dateien werden über verschiedene Arten von Strömen gelesen und geschrieben.
 Ungepufferte Ein- und Ausgabe eignet sich für schnelle Verarbeitung, während
gepufferte Ein- und Ausgabe insgesamt effizienter ist.
 Die try-With-Resources-Anweisung erleichtert das Schließen von Strömen automatisch.

Kodierung und Zeichenumwandlung

 Ein- und Ausgabekanäle können rohe Daten (Bytes) übertragen, und Zeichen sind im
UTF-16-Format dargestellt.
 Die Wahl der Kodierung ist wichtig, und Java bietet verschiedene Klassen und
Methoden, um die Kodierung anzupassen.

Serialisierung und Deserialisierung

 Serialisierung (Speichern von Objekten in einem Byte-Stream) und Deserialisierung


(Lesen von Objekten aus einem Byte-Stream) werden durch das Serializable-Interface
und die Klassen ObjectInputStream und ObjectOutputStream unterstützt.
 Es ist wichtig, die Serialisierung von Daten zu kontrollieren und sicherzustellen, dass
nur die benötigten Daten übertragen werden.

Datenformate und Effizienz

 Effiziente Datenübertragung erfordert oft benutzerdefinierte Binärformate.


 Die Wahl zwischen XML und JSON hängt von den Anforderungen ab, wobei beide
Formate auf Text basieren und für den Datenaustausch weit verbreitet sind.

Shell-Variablen und Laufzeitumgebung

 Java kann auf Shell-Variablen zugreifen, und die Runtime-Klasse bietet Methoden zur
Abfrage von Systeminformationen und zur Ausführung externer Prozesse.

Prozesssteuerung

 Die Methode exec der Runtime-Klasse ermöglicht das Starten neuer Prozesse.
 Der Zugriff auf Ein- und Ausgabeströme sowie das Warten auf Prozessbeendigung
sind wichtige Aspekte der Prozesssteuerung.

Diese Prinzipien bilden die Grundlage für den Umgang mit Dateien, I/O-Strömen und
externen Prozessen in Java.

5.4.3 Beispiel zu parallelen Prozessen

Ziel
 Berechnung von Primzahlen mithilfe des Siebs des Eratosthenes durch Prozesse,
die über Pipelines kommunizieren.

Struktur des Programms:

 Ein Controller-Prozess startet eine definierte Anzahl von Worker-Prozessen.


 Controller und Worker kommunizieren über Standard-Ein- und -Ausgaben.
 Jeder Worker-Prozess erhält eine eindeutige Nummer als Kommandozeilenargument.

Funktionsweise:

 Jeder Worker-Prozess prüft bestimmte Zahlen auf Primzahleigenschaften und gibt


diese über die Standardausgabe aus.
 Controller erstellt Worker-Prozesse, liest von ihnen gelieferte Daten, ordnet sie und
gibt sie an Worker weiter.

Herausforderungen:

 Vermeidung von endlosem Warten auf Daten zwischen den Prozessen.


 Effiziente Organisation der Kommunikation.
 Synchronisation, um Datenengpässe zu verhindern.

Verbesserungsvorschläge:

 Optimierung des Speicherverbrauchs durch dynamische Größenanpassung des Arrays


für Primzahlen.
 Berücksichtigung von Aufgabeneigenschaften zur Reduzierung des Aufwands.
 Effizientere Algorithmen für Aufgaben wie die Suche nach dem Minimum.
 Aufteilung der Controller-Funktionalität auf mehrere Prozesse, um Engpässe zu
vermeiden.
 Reduzierung unnötiger Kommunikation durch Duplizieren von Ausgabeströmen.
 Anpassung der Granularität verschickter Datenpakete für effizientere
Kommunikation.
 Überprüfung der Eignung des gewählten Ansatzes und möglicher Einsatz von Shared-
Memory.

Die parallele Programmierung bietet zahlreiche Möglichkeiten zur weiteren Optimierung der
Laufzeit und Datenverarbeitung pro Zeiteinheit. Eine sorgfältige Analyse, Anpassung und
Experimentation sind entscheidend für die Entwicklung effizienter paralleler Programme.

6.1 Grundsätzliches und Muster für Verhalten

6.1.1 Aufbau von Entwurfsmustern

Name:

 Ermöglicht die Kommunikation auf einer höheren Ebene im Softwareentwurf.


 Abstrahiert ein allgemeines Konzept im Softwareentwurf, unabhängig von konkreten
Programmen oder Programmiersprachen.
 Beispiel: Das "Visitor"-Pattern.

Problemstellung:

 Beschreibung des Problems und dessen Umfeld.


 Klärung der Bedingungen, unter denen das Entwurfsmuster anwendbar ist.
 Beispiel: Visitor ist sinnvoll, wenn viele unterschiedliche Operationen auf einer
Objektstruktur realisiert werden sollen.

Lösung:

 Beschreibung einer generellen Lösung der Problemstellung.


 Hält sich so allgemein wie möglich, um Anpassungsfähigkeit zu gewährleisten.
 Enthält mögliche Implementierungsdetails und Varianten.
 Beispiel: Die Struktur von Klassen und Abhängigkeiten im Visitor-Pattern.

Konsequenzen:

 Liste der Eigenschaften der Lösung, die als Vor- und Nachteile betrachtet werden
können.
 Ein und dieselbe Eigenschaft kann je nach Situation Vor- oder Nachteil sein.
 Beispiel: Im Visitor-Pattern ersetzt dynamisches Binden unerwünschte dynamische
Typabfragen und verbessert die Wartbarkeit.

Faustregel:

 Entwurfsmuster sind hilfreich zur Abschätzung der Konsequenzen von


Designentscheidungen.
 Ihr Einsatz sollte jedoch mit Vorsicht erfolgen, da ein exzessiver Gebrauch zu
Programmkomplexität führen kann.

6.1.2 Visitor

Eigenschaften des Visitor-Musters:

 Anwendbar, wenn viele unterschiedliche, nicht verwandte Operationen auf einer


Objektstruktur realisiert werden sollen.
 Geeignet, wenn sich die Klassen der Objektstruktur nicht ändern sollen.
 Nützlich, wenn häufig neue Operationen in die Objektstruktur integriert werden
müssen oder ein Algorithmus über die Klassen verteilt, aber zentral verwaltet werden
soll.

Struktur des Visitor-Musters:


 Objektstruktur von Visitor entspricht dem Typ "Element" und dessen Untertypen.
 Klassen werden durch Kästchen dargestellt, mit Fettschrift für Klassennamen.
 Vererbungsbeziehungen werden durch Striche und Dreiecke dargestellt.
 Methoden und Variablen in nicht-fetter Schrift.
 Abstrakte Klassen und Methoden sind kursiv, konkrete nicht kursiv.

Eigenschaften von Visitor:

 Leicht erweiterbar durch Definition neuer Untertypen von Visitor für neue
Operationen.
 Zentrale Verwaltung von verwandten Operationen im Visitor, getrennt von Visitor-
fremden Operationen.
 Möglichkeit zur Arbeit mit Objekten aus unabhängigen Klassen hierarchien.
 Gute Erweiterungsmöglichkeit der Visitor-Klassen, aber schlechte Erweiterbarkeit der
konkreten Elemente, wenn neue hinzugefügt werden.
 Die Art des Zugriffs auf konkrete Elemente durch visit-Methoden hängt von
Implementierungsdetails ab.

Ursprüngliche Beschreibung im GoF-Buch:

 Akzeptiert Methoden rufen visit-Methoden nicht direkt auf, sondern Element-Klassen


verwalten dynamische Listen von Visitors.
 Flexible Variante, aber in der Praxis weniger verbreitet aufgrund von
Verwaltungsschwierigkeiten.
 Unterschiedliche Implementierungen können das Muster stabil bewahren, solange
die grundlegenden Eigenschaften erhalten bleiben.
6.1.3 Iterator

Entwurfsmuster und Problemstellung:

 Iterator ermöglicht sequentiellen Zugriff auf Elemente eines Aggregats, ohne dessen
innere Darstellung offenzulegen.
 Anwendbar, um auf Inhalte eines Aggregats zuzugreifen, mehrere Abarbeitungen zu
ermöglichen und eine einheitliche Schnittstelle für polymorphe Iterationen
bereitzustellen.

Struktur des Iterator-Musters:

 Abstrakte Klasse/Interface "Iterator" definiert Zugriff und Abarbeitung von


Elementen.
 Klasse "ConcreteIterator" implementiert diese Schnittstelle und verwaltet die
Position.
 Abstrakte Klasse/Interface "Aggregate" definiert Schnittstelle zur Erzeugung eines
Iterators.
 Klasse "ConcreteAggregate" implementiert diese Schnittstelle und erzeugt einen
"ConcreteIterator".

Eigenschaften von Iteratoren:

 Unterstützen verschiedene Abarbeitungsvarianten für komplexe Aggregate.


 Vereinfachen die Schnittstelle von Aggregaten, da Zugriff über Iteratoren abstrahiert
wird.
 Ermöglichen gleichzeitige Abarbeitungen auf demselben Aggregat durch individuelle
Abarbeitungszustände.

Implementierungsvarianten:

 Unterscheidung zwischen internen und externen Iteratoren.


 Interne Iteratoren kontrollieren selbst den Ablauf, während externe Iteratoren von
der Anwendung gesteuert werden.
 Externe Iteratoren sind flexibler, aber interne Iteratoren sind einfacher in der
funktionalen Programmierung.
 Iteratoren können robust sein, indem sie Änderungen am Aggregat "sehen" und
darauf reagieren.

Weitere Überlegungen zur Implementierung:

 Iteratoren können durch innere Klassen in Aggregaten definiert werden, um auf


private Details zuzugreifen.
 Vorsicht bei Änderungen am Aggregat während der Iteration, um inkorrekte
Abarbeitung zu vermeiden.
 Praktisch, Iteratoren auch auf leeren Aggregaten bereitzustellen, um
Sonderbehandlungen zu vermeiden.

6.1.4 Template-Method

Definition und Anwendbarkeit:

 Template-Method definiert das Grundgerüst eines Algorithmus in einer Operation.


 Ermöglicht Unterklassen, bestimmte Schritte zu überschreiben, ohne die Struktur zu
ändern.
 Anwendbar, um den unveränderlichen Teil eines Algorithmus einmal zu
implementieren und veränderbare Teile den Unterklassen zu überlassen.
 Geeignet für gemeinsames Verhalten in Unterklassen, um Duplikate im Code zu
vermeiden.
 Kontrolle über mögliche Erweiterungen in Unterklassen durch Hooks.

Struktur des Template-Method-Musters:

 Abstrakte Klasse "AbstractClass" mit "templateMethod" und "primitiveOperationX"


Methoden.
 Konkrete Klasse "ConcreteClass" implementiert "primitiveOperationX" und erbt
"templateMethod" unverändert.

Eigenschaften von Template-Methods:


 Fundamentale Technik zur direkten Wiederverwendung von Code in
Klassenbibliotheken und Frameworks.
 Umkehrung der üblichen Kontrollstruktur ("Don’t call us, we’ll call you").
 Ruft verschiedene Arten von primitiven Operationen auf: konkrete Operationen,
abstrakte primitive Operationen, Hooks, Factory-Methods.

Spezifikation von Operationen:

 Klare Spezifikation, welche Operationen Hooks oder abstrakte Methoden sind.


 Wichtig für effektive Wiederverwendung, damit alle Beteiligten wissen, welche
Operationen in Unterklassen überschrieben werden sollen.

Implementierungsdetails:

 Primitive Operationen sind häufig protected, abstrakte Methoden sind als abstract
deklariert.
 "templateMethod" selbst sollte nicht überschrieben werden und kann final sein.
 Ziel: Minimierung der Anzahl der zu überschreibenden primitiven Operationen für
einfachere Wiederverwendung.

6.2 Erzeugende Entwurfsmuster: Factory Method

Definition:

 Factory Method (Virtual-Constructor) definiert eine Schnittstelle für die


Objekterzeugung.
 Unterklassen entscheiden, von welcher Klasse die erzeugten Objekte sein sollen.
 Die tatsächliche Erzeugung wird in Unterklassen verschoben.

Anwendbarkeit:

 Wenn eine Klasse Objekte erzeugen soll, deren Klasse nicht bekannt ist.
 Wenn eine Klasse möchte, dass Unterklassen die Art der zu erzeugenden Objekte
bestimmen.
 Um Verantwortlichkeiten an Unterklassen zu delegieren und das Delegieren lokal zu
halten.
 Wenn die Allokation und Freigabe von Objekten zentral in einer Klasse verwaltet
werden sollen.

Struktur:
 Produkt-Hierarchie: Abstrakte Klasse "Product" und konkrete Klasse
"ConcreteProduct".
 Creator-Hierarchie: Abstrakte Klasse "Creator" mit abstrakter oder default
"factoryMethod".
 ConcreteCreator: Unterklasse von "Creator" implementiert "factoryMethod".

Eigenschaften:

 Bietet Anknüpfungspunkte für Unterklassen durch vorgegebene Methoden.


 Umkehrung der Abhängigkeiten: Oberklassen hängen von Unterklassen ab.
 Verknüpft parallele Typhierarchien, was in bestimmten Fällen hilfreich sein kann.

Implementierungsdetails:

 factoryMethod kann als abstrakte Methode oder mit Default-Implementierung (Hook)


realisiert werden.
 Möglichkeit, Parameter für mehr Flexibilität hinzuzufügen, z.B., um die Art des zu
erzeugenden Produkts zu bestimmen.
 Mit Lambdas als Parameter kann der Schreibaufwand erheblich reduziert werden.

Beispiel mit Lazy-Initialization:

 Vermeidet mehrfache Erzeugung desselben Objekts durch getProduct.

Nachteil:

 Notwendigkeit, viele Unterklassen von Creator zu erstellen, kann lästig sein.


 Lambdas können den Schreibaufwand reduzieren.
6.2.2 Prototype

Definition:

 Das Prototype-Entwurfsmuster spezifiziert die Erzeugung neuer Objekte durch


Kopieren eines Prototyp-Objekts.
 Neue Objekte erhalten denselben Typ wie das Prototyp-Objekt.

Anwendbarkeit:

 Wenn Klassen, von denen Objekte erzeugt werden sollen, erst zur Laufzeit bekannt
sind.
 Um die Erzeugung einer Hierarchie von Creator-Klassen zu vermeiden (im Gegensatz
zur Factory-Method).
 Wenn jedes Objekt einer Klasse nur wenige unterschiedliche Zustände haben kann.

Struktur:

 Prototype: (Möglicherweise abstrakte) Klasse mit der Methode clone(), die sich selbst
kopiert.
 ConcretePrototype: Konkrete Unterklassen von Prototype, die clone überschreiben.
 Client: Verwendet die clone-Methode zur Erzeugung neuer Objekte.

Eigenschaften:

 Versteckt konkrete Produktklassen vor Anwendern und reduziert die Anzahl


bekannter Klassen.
 Ermöglicht die dynamische Hinzufügung und Entfernung von Prototypen zur Laufzeit.
 Erlaubt die Spezifikation neuer Objekte durch änderbare Werte.
 Vermeidet eine übermäßig große Anzahl an Unterklassen im Vergleich zur Factory-
Method.
 Ermöglicht die dynamische Konfiguration von Programmen.

Implementierungsdetails:

 Jede konkrete Unterklasse von Prototype muss die clone-Methode implementieren.


 In Java ist clone bereits in der Object-Klasse vordefiniert und kann überschrieben
werden.
 Probleme bei zyklischen Referenzen erfordern eine spezielle Implementierung von
clone.
 Prototyp-Manager können verwendet werden, um den Überblick über Prototypen zu
behalten.

Anmerkungen:

 In Sprachen wie Java wird das Cloneable-Interface verwendet, um die Verwendung von
clone zu steuern.
 Schwierigkeiten können bei der Initialisierung und Zustandsänderung nach der Kopie
auftreten.
 Besonders sinnvoll in statisch typisierten Sprachen wie C++ und Java, weniger
notwendig in dynamisch typisierten Sprachen mit direkter Unterstützung für ähnliche
Funktionalität.

6.2.3 Singleton

Definition:

 Das Singleton-Entwurfsmuster sichert zu, dass eine Klasse nur eine Instanz hat und
ermöglicht globalen Zugriff auf dieses Objekt.

Anwendbarkeit:

 Wenn genau ein Objekt einer Klasse global zugreifbar sein soll.
 Wenn die Klasse durch Vererbung erweiterbar sein soll und Anwender die erweiterte
Klasse ohne Änderungen verwenden können sollen.

Eigenschaften:

 Der Zugriff auf das einzige Objekt kann kontrolliert werden.


 Vermeidet unnötige globale Variablen.
 Unterstützt Vererbung (nicht in allen Varianten).
 Verhindert, dass Instanzen außerhalb der Kontrolle der Klasse erzeugt werden.
 Prinzipiell können auch mehrere Instanzen erzeugt werden, aber die Klasse hat die
volle Kontrolle darüber.
 Erlaubt flexiblen Zugriff auf das Singleton-Objekt durch Objektmethoden.

Implementierungsdetails:

 Die Klasse enthält eine statische Methode instance, die das einzige Objekt zurückgibt.
 Der Konstruktor ist privat, um die Instanziierung von außen zu verhindern.
 Initialisierung erfolgt beim ersten Zugriff.
 Schwierigkeiten können bei der Implementierung von verschiedenen Singleton-
Alternativen auftreten.
 Feste Verdrahtung der Alternativen in der ursprünglichen Form kann unflexibel sein.

Beispiele:

 Einfache Implementierung mit Initialisierung beim ersten Zugriff.


 Schwierigkeiten bei der Implementierung von verschiedenen Alternativen für das
Singleton-Objekt.
 Alternativen können in Untertypen implementiert werden, aber dies erfordert eine
spezifische Initialisierung.

Aktuelle Praxis:

 In modernen Anwendungen wird Singleton oft ohne Untertypen verwendet.


 Das Singleton-Objekt wird über eine globale Variable oder in Java über eine
Konstante bereitgestellt.

6.3.1 Decorator

Definition:

 Das Decorator-Entwurfsmuster, auch als Wrapper bekannt, fügt Objekten dynamisch


zusätzliche Verantwortlichkeiten hinzu und bietet eine flexible Alternative zur
Vererbung.

Anwendbarkeit:

 Um dynamisch Verantwortlichkeiten zu einzelnen Objekten hinzuzufügen, ohne


andere Objekte zu beeinflussen.
 Für Verantwortlichkeiten, die wieder entzogen werden können.
 Wenn Erweiterungen einer Klasse durch Vererbung unpraktisch sind, um eine große
Anzahl an Unterklassen zu vermeiden.

Struktur:
 Component (Window): Definiert eine Schnittstelle für Objekte, an die
Verantwortlichkeiten dynamisch hinzugefügt werden können.
 ConcreteComponent (WindowImpl): Konkrete Implementierung von Component.
 Decorator (WinDecorator): Abstrakte Klasse für Verantwortlichkeiten, die
dynamisch hinzugefügt werden können.
 ConcreteDecoratorA, ConcreteDecoratorB: Konkrete Implementierungen von
Decorator mit spezifischer Funktionalität.

Eigenschaften:

 Bietet mehr Flexibilität als statische Vererbung.


 Vermeidet Klassenüberladung in der Typhierarchie.
 Objekte von Decorator und ConcreteComponent sind nicht identisch.
 Führt zu vielen kleinen Objekten, was die Wartung erschweren kann.

Verwendung:

 Geeignet zur Erweiterung der Oberfläche oder des Erscheinungsbilds eines Objekts.
 Weniger geeignet für inhaltliche Erweiterungen oder umfangreiche Objekte.

Implementierungsdetails:

 Abstrakte Klasse oder Interface Component soll minimal gehalten werden.


 Dekoratoren können durch Verkettung beliebig kombiniert werden.
 Dekoratoren sollten nicht für inhaltliche Erweiterungen oder umfangreiche Objekte
verwendet werden.

Beispiele:

 Scroll-Bar-Dekorator für Fenster.


 Dynamisches Hinzufügen und Entfernen von Verantwortlichkeiten während der
Laufzeit.
6.3.2 Proxy

Definition:

 Ein Proxy, auch Surrogate genannt, fungiert als Platzhalter für ein anderes Objekt und
kontrolliert den Zugriff darauf. Es gibt verschiedene Anwendungsmöglichkeiten für
Proxy-Objekte, darunter Remote-Proxies, Virtual-Proxies, Protection-Proxies und
Smart-References.

Anwendbarkeit:

 Bei Bedarf für eine intelligentere Referenz auf ein Objekt als ein einfacher Zeiger.
 In Situationen, in denen ein Objekt teuer in der Erzeugung ist und erst bei Bedarf
geladen werden soll.
 Zur Kontrolle des Zugriffs auf Objekte basierend auf unterschiedlichen
Zugriffsrechten oder Situationen.

Arten von Proxies:

 Remote-Proxies:
o Platzhalter für Objekte in anderen Namensräumen, z.B., auf anderen
Rechnern.
o Leiten Nachrichten über komplexe Kommunikationskanäle weiter.
 Virtual-Proxies:
o Erzeugen Objekte bei Bedarf, um aufwendige Erzeugung zu vermeiden.
o Verzögern die Erzeugung, bis das Objekt tatsächlich benötigt wird.
 Protection-Proxies:
o Kontrollieren den Zugriff auf Objekte basierend auf Zugriffsrechten.
o Verschiedene Zugriffsrechte je nach Zugreifer oder Situation.
 Smart-References:
o Ersetzen einfache Zeiger und führen zusätzliche Aktionen bei Zugriffen durch.
o Beispiel: Referenzzählung, Laden von persistenten Objekten, Thread-
Sicherheit.

Struktur:

 Abstrakte Klasse oder Interface Subject als gemeinsame Schnittstelle für RealSubject und
Proxy.
 RealSubject: Definiert die eigentlichen Objekte, die durch Proxies repräsentiert werden.
 Proxy: Kontrolliert den Zugriff auf das eigentliche Objekt, kann für Erzeugung oder
Entfernung verantwortlich sein.

Implementierungsdetails:

 Proxies können in einer Kette miteinander verbunden werden, um unterschiedliche


Kontrollen zu ermöglichen.
 Beachtung von Referenzen auf Objekte in anderen Namensräumen oder noch nicht
existierende Objekte.

Unterschied zu Decorator:

 Ein Proxy kontrolliert den Zugriff auf ein Objekt, während ein Decorator ein Objekt
um zusätzliche Verantwortlichkeiten erweitert.

Beispiele:

 Verwendung von Proxy, um teure Objekte bei Bedarf zu erzeugen.


 Kontrolle des Zugriffs auf Objekte basierend auf unterschiedlichen Zugriffsrechten.

6.4 Entscheidungshilfen

6.4.1 Entwurfsmuster als Entscheidungshilfen

Entwurfsmuster als Entscheidungshilfen:

 Entwurfsmuster sollten nicht als festgelegte Regeln, sondern als Werkzeuge zur
Entscheidungsfindung verstanden werden.
 Zwei Sichtweisen: Wertend (positive Eigenschaften betonend) und nicht wertend
(objektive Beschreibung von Problemen und Lösungen).

Erfahrungen mit Entwurfsmustern:

 Entwurfsmuster allein führen selten zu guten Ergebnissen.


 Ideenlose Programme basieren möglicherweise auf einem falschen Umgang mit
Entwurfsmustern.
 Idee als Klammer über allen Teilen des Programms notwendig.
 Ideenlose Programme wirken komplex und wenig ansprechend.

Bewertung von Entwurfsmustern:

 Entwurfsmuster können positive oder negative Konsequenzen haben.


 Idee überwiegt und bestimmt die Qualität des Gesamtprogramms.
 Programme, die auf einer Idee basieren, enthalten oft verschiedene Entwurfsmuster,
die miteinander verstrickt sind.
Herangehensweise an den Entwurf:

 Analyse des Bedarfs und genaue Kenntnis der Anforderungen.


 Entwicklung einer einfachen Grobstruktur, die alle Anforderungen berücksichtigt.
 Kreativität und Intuition sind gefragt, um gemeinsame Merkmale aller
Anforderungen zu erkennen.

Bewusster Umgang mit Entwurfsmustern:

 Idee als Grundlage für den Entwurf.


 Entwicklung mehrerer alternativer Ideen.
 Nutzung von Entwurfsmustern zur Bewertung der Ideen.
 Gewichtung der Konsequenzen aus Entwurfsmustern.
 Beurteilungskriterium: Erwartete Gesamtkomplexität des Programms.

Kritischer Blick auf Entwurfsmuster:

 Vermeidung von ideenlosem Hinzufügen von Entwurfsmustern.


 Entwurfsmuster sollen auf natürliche Weise integriert werden, nicht durch
aufwendigen speziellen Code.

Fazit:

 Entwurfsmuster können als Werkzeug dienen, um Ideen zu entwickeln und


Programme zu strukturieren.
 Tiefes Verständnis und intuitive Anwendung der Entwurfsmuster sind entscheidend.

6.4.2 Richtlinien in der Entscheidungsfindung

Verschiedene Arten von Regeln und Richtlinien:

 Syntax- und Semantikregeln einer Programmiersprache:


o Standardisiert, Verstöße führen zu nicht lauffähigen Programmen oder
schweren Fehlern.
 Allgemeine Konventionen (Namenskonventionen):
o Verbessern Lesbarkeit, Vertrauenswürdigkeit von Programmtexten.
o Nichteinhaltung erschwert Wartung und kann zu Fehlern führen.
 Projekt-/Firmen-/Frameworks-spezifische Konventionen:
o Beeinflussen Lesbarkeit, Teambildung, Funktionalität der Software.
o Nichteinhaltung kann zu schwerwiegenden Fehlern führen.
 Faustregeln:
o Entscheidungshilfen für situationsbezogene Optionen.
o Keine Garantie für beste Wahl, jedoch nützlich bei mangelnder Information.
 Softwareentwurfsmuster:
o Hilfsmittel zur Vorhersage von Konsequenzen bei Designentscheidungen.
o Bieten genauere Abschätzungen, ermöglichen Berücksichtigung von Kriterien.
 Persönliche Expertise:
o Erfahrungsbasierte Entscheidungen, oft nicht formal ausformulierbar.
o Kann rasche und präzise Entscheidungen ermöglichen, beinhaltet jedoch auch
Fehlentscheidungen.

Umgang mit Regeln und Richtlinien:

 Einhalten von Syntax- und Semantikregeln sowie Konventionen:


o Klare Notwendigkeit, um Programmierverständnis und -ausführung zu
gewährleisten.
 Faustregeln, Entwurfsmuster und persönliche Expertise:
o Auf Wahrscheinlichkeiten basierende Entscheidungsgrundlagen.
o Internalisierung für effiziente Nutzung als persönliche Expertise.
 Komplexität von Entscheidungsprozessen:
o Persönliche Expertise als ausschlaggebender Faktor.
o Entscheidungsprozesse sind komplexer als das Befolgen von Regeln.

Programmierparadigmen und persönliche Programmierstile:

 Paradigmen als Faustregeln:


o In sich konsistente Regelsets.
o Beeinflussen persönliche Expertise.
 Fähigkeit zum Paradigmenwechsel:
o Persönliche Expertise ermöglicht bewusste Wahl zwischen Paradigmen.
o Ziel: Bewusste und gezielte Anwendung von Paradigmen und Stilen.

Das könnte Ihnen auch gefallen