Strategy-Pattern
Das Verhalten von Klassen kann über Vererbungshierarchien weitergegeben und durch Überschreiben in den erbenden Klassen verändert werden. Dies führt häufig schnell zu breiten und tiefen Vererbungsstrukturen.
Das Strategy-Pattern ist ein Entwurfsmuster, in dem Verhalten stattdessen an passende Klassen/Objekte ausgelagert (delegiert) wird.
Es wird eine Schnittstelle benötigt (Interface oder abstrakte Klasse), in dem Methoden zum Abrufen des gewünschten Verhaltens definiert werden. Konkrete Klassen leiten davon ab und implementieren das gewünschte konkrete Verhalten.
In den nutzenden Klassen wird zur Laufzeit eine passende Instanz der (Strategie-) Klassen übergeben (Konstruktor, Setter, ...) und beispielsweise über ein Attribut referenziert. Das gewünschte Verhalten muss nun nicht mehr in der nutzenden Klasse selbst implementiert werden, stattdessen wird einfach auf dem übergebenen Objekt die Methode aus der Schnittstelle aufgerufen. Dies nennt man auch "Delegation", weil die Aufgabe (das Verhalten) an ein anderes Objekt (hier das Strategie-Objekt) weiter gereicht (delegiert) wurde.
- (K3) Strategie-Entwurfsmuster praktisch anwenden
Wie kann man das Verhalten einer Klasse dynamisch ändern?
Modellierung unterschiedlicher Hunderassen: Jede Art bellt anders.
Es bietet sich an, die Hunderassen von einer gemeinsamen Basisklasse
Hund
abzuleiten, um die Hundeartigkeit allgemein sicherzustellen.
Da jede Rasse anders bellen soll, muss jedes Mal die Methode bellen
überschrieben werden. Das ist relativ aufwändig und fehleranfällig.
Außerdem kann man damit nicht modellieren, dass es beispielsweise
auch konkrete Bulldoggen geben mag, die nur leise fiepen ...
Lösung: Delegation der Aufgabe an geeignetes Objekt
Der Hund
delegiert das Verhalten beim Bellen an ein Objekt,
welches beispielsweise bei der Instantiierung der Klasse übergeben
wurde (oder später über einen Setter). D.h. die Methode Hund#bellen
bellt nicht mehr selbst, sondern ruft auf einem passenden Objekt
eine vereinbarte Methode auf.
Dieses passende Objekt ist hier im Beispiel vom Typ Bellen
und
hat eine Methode bellen
(Interface). Die verschiedenen Bell-Arten
kann man über eigene Klassen implementieren, die das Interface
einhalten.
Damit braucht man in den Klassen für die Hunderassen die Methode
bellen
nicht jeweils neu überschreiben, sondern muss nur bei
der Instantiierung eines Hundes ein passendes Bellen
-Objekt
mitgeben.
Als netten Nebeneffekt kann man so auch leicht eine konkrete Bulldogge realisieren, die eben nicht fies knurrt, sondern leise fiept ...
Entwurfsmuster: Strategy Pattern
Exkurs UML: Assoziation vs. Aggregation vs. Komposition
Eine Assoziation beschreibt eine Beziehung zwischen zwei (oder mehr) UML-Elementen (etwa Klassen oder Interfaces).
Eine Aggregation (leere Raute) ist eine Assoziation, die eine
Teil-Ganzes-Beziehung hervorhebt. Teile können dabei ohne das Ganze
existieren (Beispiel: Personen als Partner in einer Ehe-Beziehung).
D.h. auf der einbindenden Seite (mit der leeren Raute) hat man implizit
0..*
stehen.
Eine Komposition (volle Raute) ist eine Assoziation, die eine
Teil-Ganzes-Beziehung hervorhebt. Teile können aber nicht ohne das Ganze
existieren (Beispiel: Gebäude und Stockwerke: Ein Gebäude besteht aus
Stockwerken, die ohne das Gebäude aber nicht existieren.). D.h. auf der
einbindenden Seite (mit der vollen Raute) steht implizit eine 1
(ein
Stockwerk gehört genau zu einem Gebäude, ein Gebäude besteht aber aus
mehreren Stockwerken).
Siehe auch Aggregation, Assoziation und Klassendiagramm.
Zweites Beispiel: Sortieren einer Liste von Studis
Sortieren einer Liste von Studis: Collections.sort
kann eine Liste
nach einem Default-Kriterium sortieren oder aber über einen extra
Comparator
nach benutzerdefinierten Kriterien ... Das Verhalten der
Sortiermethode wird also quasi an dieses Comparator-Objekt delegiert ...
public class Studi {
private String name;
public Studi(String name) { this.name = name; }
public static void main(String[] args) {
List<Studi> list = new ArrayList<Studi>();
list.add(new Studi("Klaas"));
list.add(new Studi("Hein"));
list.add(new Studi("Pit"));
// Sortieren der Liste (Standard-Reihenfolge)?!
// Sortieren der Liste (eigene Reihenfolge)?!
}
}
Anmerkung:
Die Interfaces Comparable
und Comparator
und deren Nutzung wurde(n) in
OOP besprochen. Anonyme Klassen wurden ebenfalls in OOP besprochen. Bitte
lesen Sie dies noch einmal in der Semesterliteratur nach, wenn Sie hier
unsicher sind!
Hands-On: Strategie-Muster
Implementieren Sie das Strategie-Muster für eine Übersetzungsfunktion:
- Eine Klasse liefert eine Nachricht (
String
) mitgetMessage()
zurück. - Diese Nachricht ist in der Klasse in Englisch implementiert.
- Ein passendes Übersetzerobjekt soll die Nachricht beim Aufruf der Methode
getMessage()
in die Ziel-Sprache übersetzen.
Fragen:
- Wie muss das Pattern angepasst werden?
- Wie sieht die Implementierung aus?
Auflösung
Wrap-Up
Strategy-Pattern: Verhaltensänderung durch Delegation an passendes Objekt
- Interface oder abstrakte Klasse als Schnittstelle
- Konkrete Klassen implementieren Schnittstelle => konkrete Strategien
- Zur Laufzeit Instanz dieser Klassen übergeben (Aggregation) ...
- ... und nutzen (Delegation)
Implementieren Sie das Spiel "Schere,Stein,Papier" (Spielregeln vergleiche wikipedia.org/wiki/Schere,Stein,Papier) in Java.
Nutzen Sie das Strategy-Pattern, um den Spielerinstanzen zur Laufzeit eine konkrete Spielstrategie mitzugeben, nach denen die Spieler ihre Züge berechnen. Implementieren Sie mindestens drei unterschiedliche konkrete Strategien.
Hinweis: Eine mögliche Strategie könnte sein, den Nutzer via Tastatureingabe nach dem nächsten Zug zu fragen.
Gehen Sie bei der Lösung der Aufgabe methodisch vor:
- Stellen Sie sich eine Liste mit relevanten Anforderungen zusammen.
- Erstellen Sie (von Hand) ein Modell (UML-Klassendiagramm):
- Welche Klassen und Interfaces werden benötigt?
- Welche Aufgaben sollen die Klassen haben?
- Welche Attribute und Methoden sind nötig?
- Wie sollen die Klassen interagieren, wer hängt von wem ab?
- Implementieren Sie Ihr Modell in Java. Schreiben Sie ein Hauptprogramm, welches das Spiel startet, die Spieler ziehen lässt und dann das Ergebnis ausgibt.
- Überlegen Sie, wie Sie Ihr Programm sinnvoll manuell testen können und tun Sie das.
- [Eilebrecht2013] Patterns kompakt
Eilebrecht, K. und Starke, G., Springer, 2013. ISBN 978-3-6423-4718-4. - [Gamma2011] Design Patterns
Gamma, E. und Helm, R. und Johnson, R. E. und Vlissides, J., Addison-Wesley, 2011. ISBN 978-0-2016-3361-0. - [Kleuker2018] Grundkurs Software-Engineering mit UML
Kleuker, S., Springer Vieweg, 2018. ISBN 978-3-658-19969-2. DOI 10.1007/978-3-658-19969-2.