Login
Newsletter
Werbung

Do, 5. Juli 2012, 15:00

Objektorientierte Programmierung: Teil 4 – Strategie, wechsel Dich!

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 die vorherigen Teile der Reihe durchgelesen haben. Alle Artikel dieses Workshops finden Sie in der Übersicht.

Eine neue Strategie

Von den drei bisher verwendeten Strategien war eine dabei, deren Verhalten sehr ähnlich zu den anderen beiden war. Zur Erinnerung die drei Bot-Strategien:

  1. Immer annehmen
  2. Immer ablehnen
  3. Annahme, wenn größer gleich 200. Wenn dreimal nacheinander kleiner 200, dann immer Ablehnung; wenn dreimal nacheinander größer als 700, dann immer Annahme.

Bei der dritten Strategie ist es also so, dass man ab einem gewissen Punkt zu Strategie 1 oder 2 wechselt. Es wäre gut, wenn diese Strategie also dem Bot eine neue Strategie unterschiebt, anstatt Strategie 1 und 2 intern nachzubilden.

Hierfür gibt es natürlich auch ein Entwurfsmuster, welches sich Zustandsmuster nennt. Das Zustandsmuster besteht aus Zuständen (im Beispiel also den Strategien) und aus Zustandsübergängen, welche einen Zustand in einen anderen überführen.

Hinweis: Das Beispiel ist natürlich extrem vereinfacht. Man stelle sich aber vor, Strategie 1 und 2 müssten komplizierte Berechnungen durchführen. Diese eins zu eins in Strategie 3 nachzubilden, wäre wegen Code-Redundanz unsinnig.

Design

Die essentiellen Fragen für das Design sind:

  1. Wie kommen die konkreten Strategien an die anderen konkreten Strategien, zu denen sie wechseln sollen?
  2. Wie können sie dem Bot diese neue Strategie geschickt unterschieben?

Frage 1 ist leicht zu beantworten: Sie nutzen einfach die StrategyFactory, um die neue konkrete Strategie zu erstellen. Dafür müssen sie aber natürlich auch wissen, zu welcher Strategie sie wechseln wollen.

Hinweis: Es gibt hier auch andere Ansätze, dass z.B. der Kontext (im Beispiel die Klasse Bot) alle Strategien einmal als Instanz hält und diese herausgeben kann, sodass ein Wechsel möglich ist.

Frage 2 ist etwas schwieriger zu beantworten. Bisher war es so, dass im Paketdiagramm libbot immer libstrategy nutzt. Würde man nun von den konkreten Strategien direkt auf Bot zugreifen, hätte man auch eine Abhängigkeit von libstrategy zu libbot und damit eine zyklische Paketabhängigkeit geschaffen, was in den seltensten Fällen gut endet.

Um dieses Problem zu lösen, hilft das Dependency Inversion Principle. Hier könnte man ein Interface IBot unter die Klasse Bot legen, welches die Strategien nutzen könnte. Natürlich ist die Abhängigkeit immer noch zyklisch, wenn IBot im Paket libbot liegt. Man könnte das Interface also in libstrategy auslagern, aber das passt kontextuell nicht mehr in das Strategie-Paket. Ein einzelnes Paket mit dem Interface ist aber ebenso übertrieben.

Was ist also die Lösung? Ganz simpel: Eine Namensänderung. Wieso heißt das Interface IBot? Es soll eigentlich nur eine Schnittstelle anbieten, damit Strategien den umliegenden Kontext verändern können. Vor allem ist nicht gesagt, dass in ferner Zukunft niemand die Strategien auch anders als für einen Bot einsetzen will.

Aus dem Grund ist die Integration des Interfaces in das Paket libstrategy korrekt, der Name sollte aber IStrategyContext lauten und die Klasse sollte nur eine Operation setStrategy besitzen.

Das Interface IStrategyContext und deren Nutzer

Dominik Wagenführ

Das Interface IStrategyContext und deren Nutzer

Dennoch fehlt etwas: Die konkreten Strategien müssen auch an den Kontext kommen, also an den Nutzer der jeweiligen Strategie. Dies geschieht am besten, indem die Basisklasse unter den Strategien den Kontext IStrategyContext verwaltet und bei Bedarf an die Generalisierungen gibt. Dies ist dann auch der Grund, wieso das Interface IStrategy der Basisklasse BaseStrategy weichen muss.

Jetzt fehlt nur noch die Beschreibung der Zustände. Diese entsprechen im vorliegenden Fall den drei konkreten Strategien. Als Zustandsübergang funktioniert die Methode acceptOffer, wobei der jeweilige Übergang aus dem Zustand variable heraus an eine Bedingung geknüpft ist:

  • Liegt der übergebene Wert zwischen 200 und 700 (inklusive), bleibt man in dem Zustand und setzt die Zählwerte mNumDeclineInRow und mNumAcceptInRow zurück.
  • Ist der Wert kleiner als 200 und hat man schon dreimal abgelehnt, geht man in den Zustand decline über.
  • Ist der Wert größer als 700 und hat man schon dreimal angenommen, geht man in den Zustand accept über.

Die Zustände accept und decline können dann nur noch durch das Spielende verlassen werden.

Achtung: Der Zustandsautomat hat eine kleine Unschärfe und ist der Einfachheit halber nicht einhunderprozentig korrekt dargestellt. So fehlt nämlich die eigentliche Aktion des Zählens, wie oft ein Angebot abgelehnt oder angenommen wurde. Wenn man es sehr genau nehmen würde, dann müsste man hierfür Zwischenzustände einbauen, deren einzige Aufgabe es wäre, die jeweilige Anzahl an Ablehnungen bzw. Annahmen zu zählen. Sie wechseln nach dem Zählen ohne Zustandsübergang direkt in den nächsten Zustand, der eben von der Anzahl abhängt.

Pro-Linux
Pro-Linux @Facebook
Neue Nachrichten
Werbung