Objektorientierte Programmierung: Teil 2 - Die richtige Strategie
Der Begriff der objektorientierten Programmierung (kurz OOP) existiert schon eine ganze Weile. Wer zuvor prozedural programmiert hat, erwischt sich beim Übergang zu OOP öfter dabei, wie er die früheren Funktionen einfach mit einer Klasse umgibt und dies als objektorientierte Programmierung verkauft. Die Artikelreihe soll an einem einfachen Beispiel zeigen, was man in so einem Fall besser machen könnte.
Hinweis: Bevor man im Artikel fortfährt, sollte man sich den vorherigen Teil der Reihe durchgelesen haben (siehe Objektorientierte Programmierung: Teil 1). Alle Artikel dieses Workshops finden Sie in der Übersicht.
Die Wahl der richtigen Strategie
Ein sehr beliebtes und bekanntes Entwurfsmuster ist das Strategie-Muster. Es dient vorrangig dazu, verschiedene Verhalten als Klassen umzusetzen, die von einer Basisklasse abgeleitet sind. »Außen« nutzt man dann nur diese Basisklasse, die konkreten Strategien sind dem Nutzer unbekannt. Zusätzlich ist es möglich, zur Laufzeit einer Klasse ein neues Verhalten/eine neue Strategie zu geben.
Dies wurde ansatzweise auch schon in der letzten Umsetzung gemacht, bei der jeder konkrete Bot von einer Basisklasse BaseBot
abgeleitet war. Ein großer Unterschied besteht aber darin, dass es damals kein Verhalten war, dass man erstellt hat, sondern ein bestimmter Typ.
Design
Das Strategie-Entwurfsmuster wird eins zu eins umgesetzt, wie es von der »Gang of Four« vor langer Zeit definiert wurde. Da die Basisklasse aller konkreten Strategien keinerlei Logik umfasst, wird ein Interface IStrategy
benutzt.
Der Bot enthält dann dieses Interface als Strategie. Hierfür wurde eine Aggregation gewählt, weil die Strategie auch ohne Bot existieren kann (und wird), da sie von Game
erzeugt wird. Der Bot übernimmt dann nur noch dessen Zerstörung beim Beenden.
Klassenaufteilung
Klasse:
IStrategy
Benötigt:
- -
Verantwort.:
- Interface für die konkreten Strategien, um ein Angebot anzunehmen oder abzulehnen
Klasse:
AcceptStrategy
Basisklasse:
IStrategy
Benötigt:
- -
Verantwort.:
- nimmt ein Angebot immer an
Klasse:
DeclineStrategy
Basisklasse:
IStrategy
Benötigt:
- -
Verantwort.:
- lehnt ein Angebot immer ab
Klasse:
VariableStrategy
Basisklasse:
IStrategy
Benötigt:
- -
Verantwort.:
- entscheidet variabel, ob ein Angebot abgelehnt oder angenommen wird
Klasse:
Bot
Benötigt:
IStrategy
Verantwort.:
- zählt die angenommenen Punkte
Klasse:
Game
Benötigt:
Bot
,VariableStrategy
,IStrategy
,AcceptStrategy
,DeclineStrategy
Verantwort.:
- erstellt den Bot und die richtige Strategie dazu; liest Benutzereingabe und fragt Bot nach Annahme oder Ablehnung
Abhängigkeiten
Das Interface IStrategy
und die konkreten Strategien haben keine Abhängigkeiten.
Der Bot ist nur vom Interface IStrategy
abhängig, nicht aber von den konkreten Strategien.
Da die Klasse Game
die konkreten Strategien erstellen muss, hängt es von diesen auch ab. Zusätzlich erstellt sie auch den Bot und gibt dessen Punkte am Ende aus.
Vor- und Nachteile
Das Design der Klasse Bot
ähnelt sehr stark dem ersten Ansatz im ersten Teil. Der große Unterschied ist, dass die Strategie durch ein echtes Objekt und nicht nur durch einen String repräsentiert werden muss.
Der Vorteil ist nun, dass der Bot dank des Strategie-Musters die konkreten Strategien nicht kennen muss und völlig losgelöst davon arbeiten kann. Mit diesem Design hat man das Design-Prinzip »Programmiere gegen Schnittstellen, nicht gegen Implementierungen« umgesetzt.
Auch hat der Bot
nun wirklich nur eine Aufgabe, nämlich Punkte zählen. Die Entscheidung, ob ein Angebot angenommen oder abgelehnt wird, muss er selbst nicht mehr treffen, dies übernimmt seine gesetzte Strategie.
Das Strategie-Muster hat auch den Vorteil, dass zusätzliche, ganz andere Verhaltensweisen des Bots leicht ergänzt werden können. Hätte der Bot zwei weitere Verhaltensarten, die es in jeweils drei unterschiedlichen Ausprägungen gibt, erweitert man die Klasse Bot um zwei Interfaces und fügt insgesamt acht neue Klassen (pro Verhalten ein Interface und drei Realisierungen) hinzu.
Der Nachteil der Erweiterbarkeit bleibt aber nach wie vor. Es ist zwar extrem leicht, eine neue Strategie zu realisieren und diese dem Bot unterzuschieben, aber dennoch muss die Klasse Game
alle konkreten Strategien kennen. Wird eine neue Strategie hinzugefügt, muss auch Game
angepasst werden. Das ist also immer noch nicht die ultimative Lösung.
Implementierung
Die C++-Implementierung der obigen Klassen kann als Archiv heruntergeladen werden: oop3-beispiel.tar.gz.