1 Entwurfsmuster

Beantworten Sie kurz und bündig folgende Fragen!

  • Welche Vorteile ergeben sich durch den Einsatz eines Entwurfsmusters?

    • Wiederverwendung von erprobten Lösungen
    • Verbesserte Qualität (Verständlichkeit, Wartbarkeit)
    • Kostenersparnis durch effizienteren Entwicklungsprozess
  • Entwurfsmuster werden in drei Kategorien unterteilt. Nennen Sie die Kategorien sowie jeweils ein passendes Entwurfsmuster:

    • Creational: Singleton – stellt sicher, dass eine Klasse nur ein Exemplar hat und bietet einen globalen Zugriffspunkt darauf.
    • Structural: Adapter – ermöglicht das Zusammenarbeiten von Klassen mit inkompatiblen Schnittstellen, indem es die Schnittstelle einer Klasse in eine andere Schnittstelle übersetzt.
    • Behavioural: Observer – definiert eine Abhängigkeit zwischen Objekten, sodass, wenn ein Objekt seinen Zustand ändert, alle abhängigen Objekte darüber benachrichtigt und automatisch aktualisiert werden.
  • Manche Entwurfsmuster werden oftmals miteinander kombiniert und arbeiten zusammen. Geben Sie hierfür zwei Beispiele an und erklären diese kurz:

    • Behavioural + Structural: Visitor + Composite – Ermöglicht verschiedene Operationen auf einer Objektstruktur durchzuführen, ohne die Objekte selbst zu ändern. Besonders nützlich, wenn viele Objekttypen vorhanden sind und je nach Typ unterschiedliche Aktionen ausgeführt werden sollen.
    • Behavioural: Iterator + Composite – Vereinfacht den Zugriff und die Durchquerung von komplexen Strukturen. Der Iterator ermöglicht es, alle Elemente einer Struktur wie durch eine einfache Liste zu durchlaufen, was besonders bei zusammengesetzten Strukturen wie Bäumen hilfreich ist.

2 Entwurf von Klassenhierarchien

Wir betrachten in dieser Aufgabe Terme über die Rechenarten , die rekursiv definiert sind:

  • Definition:

    • Jedes Literal ist ein Term, z.B. „4“.
    • Ist ein Term, so ist „()“ ein (geklammerter) Term.
    • Sind , Terme, so ist „“ ebenso ein Term.
  • Beispiele für gültige Terme:

    • , , oder .

Aufgaben:

a) Modellieren Sie eine Klassenstruktur in UML

Mit Hilfe dieser Struktur soll eine rekursive Struktur von Termen erstellt werden. Sehen Sie mindestens einzelne Klassen für die Addition und Multiplikation vor sowie weitere Klassen für geklammerte Terme und Literale, die ganze Zahlen repräsentieren.

b) Zeichnen Sie das UML-Klassendiagramm.

classDiagram
    Term <|-- Literal
    Term <|-- BinaryOperator
    BinaryOperator <|-- Addition
    BinaryOperator <|-- Multiplication
    Term <|-- Brackets

    class Term {
        <<abstract>>
        + accept(visitor: Visitor)
    }

    class Literal {
        - value : int
        + accept(visitor: Visitor)
    }

    class BinaryOperator {
        <<abstract>>
        - left : Term
        - right : Term
        + accept(visitor: Visitor)
    }

    class Addition {
        + accept(visitor: Visitor)
    }

    class Multiplication {
        + accept(visitor: Visitor)
    }

    class Brackets {
        - inner : Term
        + accept(visitor: Visitor)
    }

U Decorator

Modellieren Sie folgenden Sachverhalt als UML-Klassendiagramm. Gehen Sie dabei davon aus, dass für die Implementierung Java zu benutzen wäre. Sie müssen jedoch keine Implementierung erstellen, sondern nur das UML-Klassendiagramm. Sie werden feststellen, dass ein naiver Ansatz problematisch ist. Besser ist es, auf ein passendes Entwurfsmuster zurückzugreifen. Welches Entwurfsmuster wäre hierfür geeignet?

Eine Grafikbibliothek soll die Verwendung folgender Fenstertypen erlauben:

  • einfache Fenster ohne Zusatzfunktionalität
  • Fenster, die eine Titelleiste haben
  • Fenster, die eine Statusleiste haben
  • Fenster, die horizontal und vertikal „scrollbar“ sind
  • alle daraus konstruierbaren „Featurekombinationen“, wie z. B. ein Fenster mit Titelleiste das horizontal und vertikal „scrollbar“ ist

Empfohlenes Entwurfsmuster:
Decorator Pattern

Das Decorator Pattern verhindert die exponentielle Explosion von Klassen, die bei einem naiven Ansatz entstehen würde, indem es die Flexibilität bietet, Features dynamisch zu kombinieren.


U Visitor Pattern

In dieser Aufgabe sollen Sie das Visitor-Pattern anwenden. Wir möchten dabei einen Visitor für die Klassenstruktur aus Aufgabe 2 implementieren.

Es soll möglich sein, Terme auf der Konsole auszugeben oder deren Wert zu berechnen.

a) Erweiterung des Klassendiagramms aus Aufgabe 2 um die Klasse Visitor

classDiagram
    Term <|-- Literal
    Term <|-- BinaryOperator
    BinaryOperator <|-- Addition
    BinaryOperator <|-- Multiplication
    Term <|-- Brackets
    Term <|.. Visitor

    class Term {
        <<abstract>>
        + accept(visitor: Visitor)
    }

    class Literal {
        - value : int
        + accept(visitor: Visitor)
    }

    class BinaryOperator {
        <<abstract>>
        - left : Term
        - right : Term
        + accept(visitor: Visitor)
    }

    class Addition {
        + accept(visitor: Visitor)
    }

    class Multiplication {
        + accept(visitor: Visitor)
    }

    class Brackets {
        - inner : Term
        + accept(visitor: Visitor)
    }

    class Visitor {
        <<interface>>
        + visitLiteral(literal: Literal)
        + visitAddition(addition: Addition)
        + visitMultiplication(multiplication: Multiplication)
        + visitBrackets(brackets: Brackets)
    }

b) Implementierung

Fertigen Sie eine Implementierung für diese Klassenstruktur an, welche die oben genannten Terme anlegt und über die Visitor-Klassen ausgibt bzw. den Wert berechnet.

// Visitor Interface
public interface Visitor {
    void visitLiteral(Literal literal);
    void visitAddition(Addition addition);
    void visitMultiplication(Multiplication multiplication);
    void visitBrackets(Brackets brackets);
}
 
// Concrete Visitor for Printing
public class PrintVisitor implements Visitor {
    @Override
    public void visitLiteral(Literal literal) {
        System.out.print(literal.getValue());
    }
 
    @Override
    public void visitAddition(Addition addition) {
        System.out.print("(");
        addition.getLeft().accept(this);
        System.out.print(" + ");
        addition.getRight().accept(this);
        System.out.print(")");
    }
 
    @Override
    public void visitMultiplication(Multiplication multiplication) {
        System.out.print("(");
        multiplication.getLeft().accept(this);
        System.out.print(" * ");
        multiplication.getRight().accept(this);
        System.out.print(")");
    }
 
    @Override
    public void visitBrackets(Brackets brackets) {
        System.out.print("(");
        brackets.getInner().accept(this);
        System.out.print(")");
    }
}
 
// Concrete Visitor for Evaluation
public class EvalVisitor implements Visitor {
    private int result;
 
    public int getResult() {
        return result;
    }
 
    @Override
    public void visitLiteral(Literal literal) {
        result = literal.getValue();
    }
 
    @Override
    public void visitAddition(Addition addition) {
        addition.getLeft().accept(this);
        int left = result;
        addition.getRight().accept(this);
        int right = result;
        result = left + right;
    }
 
    @Override
    public void visitMultiplication(Multiplication multiplication) {
        multiplication.getLeft().accept(this);
        int left = result;
        multiplication.getRight().accept(this);
        int right = result;
        result = left * right;
    }
 
    @Override
    public void visitBrackets(Brackets brackets) {
        brackets.getInner().accept(this);
        // Result remains the same
    }
}
 
// Beispielnutzung
public class Main {
    public static void main(String[] args) {
        // Ausdruck: 4 + (5 * 6)
        Term expression = new Addition(
            new Literal(4),
            new Brackets(
                new Multiplication(
                    new Literal(5),
                    new Literal(6)
                )
            )
        );
 
        // Ausgabe des Ausdrucks
        PrintVisitor printVisitor = new PrintVisitor();
        expression.accept(printVisitor);
        System.out.println();
 
        // Berechnung des Wertes
        EvalVisitor evalVisitor = new EvalVisitor();
        expression.accept(evalVisitor);
        System.out.println("Wert: " + evalVisitor.getResult());
    }
}

Ausgabe:

(4 + (5 * 6))
Wert: 34

Beispiel

Ausdrucksstruktur:
Der Ausdruck 4 + (5 * 6) wird als Baum dargestellt:

  • Addition
    • Literal: 4
    • Brackets
      • Multiplication
        • Literal: 5
        • Literal: 6

Traversierung durch Visitor:

  • PrintVisitor: Gibt die Struktur mit Klammern und Operatoren aus.
  • EvalVisitor: Berechnet zuerst 5 * 6 = 30 und dann 4 + 30 = 34.

K Entwurfsmuster

a) Beobachter Entwurfsmuster (Observer-Pattern)

Stellen Sie das Beobachter Entwurfsmuster (Observer-Pattern) in einem UML-Klassendiagramm dar (inkl. wichtige Methoden aber ohne deren Inhalt sowie ohne Multiplizitäten und Rollennamen). Abstrakte Klassen können Sie ggf. mit [abstract] unter dem Klassennamen markieren. Damit Sie Ihre Lösung besser einschätzen können, bekommen Sie für diese Aufgabe eine Bewertung in Punkten. Die maximale Punktzahl ist 8. Diese Punkte dienen lediglich der Einschätzung Ihrer Lösung und sind keine Bonuspunkte.

classDiagram
    Subject <|-- ConcreteSubject
    Observer <|-- ConcreteObserver
    Subject <|-- Observer
        ConcreteObserver <|-- ConcreteSubject
    class Subject {
        <<interface>>
        + attach(observer: Observer)
        + detach(observer: Observer)
        + notify()
    }

    class ConcreteSubject {
        - state : String
        + attach(observer: Observer)
        + detach(observer: Observer)
        + notify()
        + getState() : String
        + setState(state: String)
    }

    class Observer {
        <<interface>>
        + update(subject: Subject)
    }

    class ConcreteObserver {
        - observerState : String
        + update(subject: Subject)
    }

Dieses Muster fördert lose Kopplung zwischen Subjekt und Beobachtern, wodurch die Wartbarkeit und Erweiterbarkeit des Systems verbessert wird.