In der ob­jekt­ori­en­tier­ten Pro­gram­mie­rung un­ter­stüt­zen Design Patterns (Ent­wurfs­mus­ter) die Ent­wick­ler mit bewährten Lö­sungs­an­sät­zen und -scha­blo­nen. Ist das passende Lö­sungs­sche­ma gefunden, müssen nur noch in­di­vi­du­el­le An­pas­sun­gen vor­ge­nom­men werden. Derzeit gibt es insgesamt 70 Ent­wurfs­mus­ter, die auf bestimmte Ein­satz­ge­bie­te zu­ge­schnit­ten sind. Strategy Design Patterns fo­kus­sie­ren das Verhalten von Software.

Was ist das Strategy Pattern?

Das Strategy Pattern gehört zu den Be­ha­vi­oral Patterns (Ver­hal­tens­mus­tern), die eine Software mit ver­schie­de­nen Lö­sungs­me­tho­den aus­stat­ten. Hinter den Stra­te­gien steht eine Familie von Al­go­rith­men, die vom ei­gent­li­chen Programm ab­ge­grenzt werden und autonom (= aus­tausch­bar) sind. Zu einem Strategie-Ent­wurfs­mus­ter gehören auch gewisse Vorgaben und Hil­fe­stel­lun­gen für Ent­wick­ler. So be­schrei­ben Strategy Patterns, wie man Klassen aufbaut, eine Gruppe von Klassen ar­ran­giert und Objekte erstellt. Eine Be­son­der­heit des Strategy Design Patterns ist, dass ein variables Programm- und Ob­jekt­ver­hal­ten auch zur Laufzeit einer Software rea­li­siert werden kann.

Wie sieht die UML-Dar­stel­lung eines Strategy Patterns aus?

Strategy Patterns werden nor­ma­ler­wei­se mit der gra­fi­schen Mo­del­lie­rungs­spra­che UML (Unified Modelling Language) entworfen. Sie vi­sua­li­siert Ent­wurfs­mus­ter mit einer stan­dar­di­sier­ten Notation und verwendet dabei spezielle Zeichen und Symbole. Die UML stellt für die ob­jekt­ori­en­tier­te Pro­gram­mie­rung ver­schie­de­ne Dia­gramm­ty­pen zur Verfügung. Für die Dar­stel­lung eines Strategie-Ent­wurfs­mus­ters wird in der Regel ein Klas­sen­dia­gramm mit min­des­tens drei Grund­kom­po­nen­ten gewählt:

  • Context (Kontext bzw. Kontext-Klasse)
  • Strategy (Strategie bzw. Strategie-Klasse)
  • Con­cre­teStra­tegy (Konkrete Strategie)

Im Strategy Design Pattern über­neh­men die Grund­kom­po­nen­ten spezielle Funk­tio­nen: Die Ver­hal­tens­mus­ter der Context-Klasse werden in ver­schie­de­ne Strategy-Klassen aus­ge­la­gert. Diese separaten Klassen be­her­ber­gen die Al­go­rith­men, die als Con­cre­teStra­te­gies be­zeich­net werden. Über eine Referenz (also einen internen Verweis) kann der Context bei Bedarf auf die aus­ge­la­ger­ten Be­rech­nungs­va­ri­an­ten (Con­cre­teStra­tegyA, Con­cre­teStra­tegyB etc.) zugreifen. Dabei in­ter­agiert er nicht direkt mit den Al­go­rith­men, sondern mit einer Schnitt­stel­le.

Das Strategy-Interface kapselt die Be­rech­nungs­va­ri­an­ten und kann zugleich von allen Al­go­rith­men im­ple­men­tiert werden. Für die In­ter­ak­ti­on mit dem Context stellt die ge­ne­ri­sche Schnitt­stel­le nur eine einzige Methode zum Auslösen von Con­cre­teStra­tegy-Al­go­rith­men zur Verfügung. Zu den In­ter­ak­tio­nen mit dem Context gehört neben dem Stra­te­gie­auf­ruf auch der Austausch von Daten. An Stra­te­gie­wech­seln, die auch zur Laufzeit eines Programms statt­fin­den können, ist das Strategy-Interface ebenfalls beteiligt.

Fakt

Mit einer Kapselung wird der direkte Zugriff auf Al­go­rith­men und interne Da­ten­struk­tu­ren un­ter­bun­den. Ein externe Instanz (Client, Context) kann Be­rech­nun­gen und Funk­tio­nen aus­schließ­lich über de­fi­nier­te Schnitt­stel­len in Anspruch nehmen. Dabei sind nur die­je­ni­gen Methoden und Da­ten­ele­men­te eines Objekts zu­gäng­lich, die für die externe Instanz relevant sind.

Wie das Ent­wurfs­mus­ter in einem pra­xis­na­hen Projekt umgesetzt wird, erklären wir nun anhand eines Strategy-Pattern-Beispiels.

Das Strategy Pattern am Beispiel erklärt

In unserem Beispiel (wir ori­en­tie­ren uns an dem Stu­di­en­pro­jekt zum Strategy Pattern von Philipp Hauer) soll eine Na­vi­ga­ti­ons-App mithilfe eines Strategie-Ent­wurfs­mus­ters rea­li­siert werden. Die App soll eine Rou­ten­be­rech­nung durch­füh­ren, die sich an den üblichen Trans­port­mit­teln ori­en­tiert. Der Nutzer kann zwischen drei Optionen wählen:

  • Fußgänger (Con­cre­teStra­tegyA)
  • Auto (Con­cre­teStra­tegyB)
  • Nah­ver­kehr (Con­cre­teStra­tegyC)

Überträgt man diese Vorgaben in eine UML-Grafik, werden Aufbau und Funk­ti­ons­wei­se des be­nö­tig­ten Strategy Patterns deutlich:

Der Client ist in unserem Beispiel die Be­dien­ober­flä­che (Graphical User Inferface, GUI) einer Na­vi­ga­ti­ons-App mit Schalt­flä­chen für die Rou­ten­be­rech­nung. Trifft der Anwender eine Auswahl und tippt auf eine Schalt­flä­che, wird eine konkrete Route kal­ku­liert. Der Context (Navigator-Klasse) hat die Aufgabe, eine Reihe von Kon­troll­punk­ten auf der Karte zu errechnen und dar­zu­stel­len. Die Navigator-Klasse verfügt über eine Methode zum Um­schal­ten der aktiven Routing-Strategie. So kann über die Client-Schalt­flä­chen zwischen den Trans­port­mit­teln pro­blem­los ge­wech­selt werden.

Löst man etwa mit der Fußgänger-Schalt­flä­che des Clients einen ent­spre­chen­den Befehl aus, wird der Service „Berechne die Fußgänger-Route“ (Con­cre­teStra­tegyA) an­ge­for­dert. Die Methode exe­cu­te­Al­go­rithm() (in unserem Beispiel die Methode: be­rechne­Rou­te (A, B)) ak­zep­tiert einen Ursprung und ein Ziel und gibt eine Sammlung der Kon­troll­punk­te der Route zurück. Der Context nimmt den Client-Befehl entgegen und ent­schei­det auf der Basis von vorher de­fi­nier­ten Richt­li­ni­en (Policy) über die passende Strategie (setStra­tegy: Fußgänger). Per Call delegiert er die Anfrage an das Strategy-Objekt und dessen Schnitt­stel­le.

Durch get­Stra­tegy() wird die aktuell gewählte Strategie im Context (Navigator-Klasse) hin­ter­legt. Die Er­geb­nis­se der Con­cre­teStra­tegy-Be­rech­nun­gen fließen in die weitere Auf­be­rei­tung sowie in die grafische Rou­ten­dar­stel­lung in der Na­vi­ga­ti­ons-App ein. Ent­schei­det sich der Anwender für eine andere Route, indem er bei­spiels­wei­se danach auf die Schalt­flä­che „Auto“ klickt, wechselt der Context auf die an­ge­frag­te Strategie (Con­cre­teStra­tegyB) und ver­an­lasst über einen weiteren Call eine neue Be­rech­nung. Am Ende der Prozedur wird eine mo­di­fi­zier­te Weg­be­schrei­bung für das Trans­port­mit­tel Auto aus­ge­ge­ben.

In unserem Beispiel kann die Pattern-Mechanik mit relativ über­sicht­li­chem Code umgesetzt werden:

Context:

public class Context {
    //vorgegebener Standardwert (Default-Verhalten): ConcreteStrategyA
    private Strategy strategy = new ConcreteStrategyA(); 
    public void execute() { 
        //delegiert das Verhalten an ein Strategy-Objekt
        strategy.executeAlgorithm(); 
    }
    public void setStrategy(Strategy strategy) {
        strategy = strategy;
    }
    public Strategy getStrategy() { 
        return strategy; 
    } 
}

Strategy, Con­cre­teStra­tegyA, Con­cre­teStra­tegyB:

interface Strategy { 
    public void executeAlgorithm(); 
} 
class ConcreteStrategyA implements Strategy { 
    public void executeAlgorithm() { 
        System.out.println("Concrete Strategy A"); 
    } 
} 
class ConcreteStrategyB implements Strategy { 
    public void executeAlgorithm() { 
        System.out.println("Concrete Strategy B"); 
    } 
}

Client:

public class Client { 
    public static void main(String[] args) { 
        //Default-Verhalten 
        Context context = new Context(); 
        context.execute(); 
        //Verhalten ändern 
        context.setStrategy(new ConcreteStrategyB()); 
        context.execute(); 
    } 
}

Was sind die Vor- und Nachteile des Strategy Patterns?

Die Vorteile eines Strategy Patterns werden erkennbar, wenn man die Per­spek­ti­ve eines Pro­gram­mie­rers und Sys­tem­ad­mi­nis­tra­tors einnimmt. Generell führt die Zerlegung in autonome Module und Klassen zu einer besseren Struk­tu­rie­rung des Pro­gramm­codes. In den ab­ge­grenz­ten Teil­be­rei­chen hat es der Pro­gram­mie­rer unserer Beispiel-App mit schlan­ke­ren Code-Segmenten zu tun. So lässt sich der Umfang der Navigator-Klasse durch die Aus­la­ge­rung der Stra­te­gien ver­rin­gern, und auf eine Bildung von Un­ter­klas­sen kann im Bereich des Kontextes ver­zich­tet werden.

Da im schlan­ke­ren und sauber ab­ge­grenz­ten Code die internen Ab­hän­gig­kei­ten von Segmenten im Rahmen bleiben, haben Än­de­run­gen geringere Aus­wir­kun­gen. Sie ziehen also seltener weitere – mög­li­cher­wei­se lang­wie­ri­ge – Um­pro­gram­mie­run­gen nach sich; teils können diese sogar ganz aus­ge­schlos­sen werden. Über­sicht­li­che­re Code-Segmente lassen sich auch lang­fris­tig besser pflegen, zudem werden die Pro­blem­dia­gno­se und die Feh­ler­su­che er­leich­tert.

Darüber hinaus pro­fi­tiert die Bedienung, da die Beispiel-App mit einer be­nut­zer­freund­li­chen Ober­flä­che aus­ge­stat­tet werden kann. Über deren Schalt­flä­chen können Anwender das Pro­gramm­ver­hal­ten (Rou­ten­be­rech­nung) auf einfache Weise variabel steuern und bequem zwischen Optionen auswählen.

Da der Context der Na­vi­ga­ti­ons-App durch die Kapselung der Al­go­rith­men nur mit einer Schnitt­stel­le in­ter­agiert, ist er un­ab­hän­gig von der konkreten Im­ple­men­tie­rung einzelner Al­go­rith­men. Werden zu einem späteren Zeitpunkt die Al­go­rith­men geändert oder neue Stra­te­gien ein­ge­führt, muss der Code des Contexts nicht geändert werden. So könnte man die Rou­ten­be­rech­nung schnell und un­kom­pli­ziert mit zu­sätz­li­chen Con­cre­teStra­te­gies für Flug­zeug­rou­ten, Schiffs- und Fern­ver­kehr ergänzen. Die neuen Stra­te­gien müssen lediglich das Strategy-Interface korrekt im­ple­men­tie­ren.

Strategy Patterns er­leich­tern die ohnehin schon schwie­ri­ge Pro­gram­mie­rung ob­jekt­ori­en­tier­ter Software durch eine weitere positive Ei­gen­schaft. Sie er­mög­li­chen den Entwurf mehrfach- und wie­der­ver­wend­ba­rer Software(-Module), deren Ent­wick­lung als besonders an­spruchs­voll gilt. So könnten auch verwandte Context-Klassen die aus­ge­la­ger­ten Stra­te­gien zur Rou­ten­be­rech­nung via Interface nutzen und müssen sie nicht mehr selbst im­ple­men­tie­ren.

Trotz der zahl­rei­chen Vorteile hat das Strategy Pattern auch einige Nachteile. Der Software-Entwurf erzeugt durch seinen kom­ple­xe­ren Aufbau mög­li­cher­wei­se Red­un­dan­zen und In­ef­fi­zi­en­zen in der internen Kom­mu­ni­ka­ti­on. So kann die ge­ne­ri­sche Strategy-Schnitt­stel­le, die ja alle Al­go­rith­men glei­cher­ma­ßen im­ple­men­tie­ren müssen, im Ein­zel­fall über­di­men­sio­niert sein.

Ein Beispiel: Nachdem der Context gewisse Parameter erstellt und in­itia­li­siert hat, übergibt er sie an die ge­ne­ri­sche Schnitt­stel­le und die darin de­fi­nier­te Methode. Die letztlich im­ple­men­tier­te Strategie benötigt aber nicht unbedingt alle kom­mu­ni­zier­ten Context-Parameter und ver­ar­bei­tet sie dem­zu­fol­ge auch nicht. Eine be­reit­ge­stell­te Schnitt­stel­le wird im Strategy Pattern also nicht immer optimal genutzt und ein erhöhter Kom­mu­ni­ka­ti­ons­auf­wand mit über­flüs­si­gen Da­ten­trans­fers lässt sich nicht immer vermeiden.

Bei der Im­ple­men­tie­rung gibt es zudem eine enge interne Ab­hän­gig­keit zwischen Client und Stra­te­gien. Da der Client die Auswahl trifft und die konkrete Strategie per Aus­lö­se­be­fehl anfordert (in unserem Beispiel die Be­rech­nung der Fuß­gän­ger­rou­te), muss er die Con­cre­teStra­te­gies kennen. Man sollte daher dieses Ent­wurfs­mus­ter nur verwenden, wenn Strategie- und Ver­hal­tens­wech­sel wichtig bzw. elementar für die Ver­wen­dung und die Funk­ti­ons­wei­se einer Software sind.

Die auf­ge­führ­ten Nachteile können teilweise umgangen oder aus­ge­gli­chen werden. So wird die Zahl der Ob­jekt­in­stan­zen, die im Strategy Pattern in größerer Zahl anfallen können, häufig durch eine Im­ple­men­tie­rung in ein Flyweight Pattern reduziert. Die Maßnahme wirkt sich auch positiv auf die Effizienz und den Spei­cher­be­darf einer Anwendung aus.

Wo kommt das Strategy Pattern zum Einsatz?

Das Strategy Design Pattern ist als grund­le­gen­des Ent­wurfs­mus­ter in der Software-Ent­wick­lung nicht auf ein be­stimm­tes Ein­satz­ge­biet be­schränkt. Vielmehr ist die Art der Pro­blem­stel­lung aus­schlag­ge­bend für die Ver­wen­dung des Design Patterns. Software, die an­ste­hen­de Aufgaben und Probleme mit Va­ria­bi­li­tät, Ver­hal­tens­op­tio­nen und -än­de­run­gen lösen muss, ist prä­de­sti­niert für das Ent­wurfs­mus­ter.

So greifen Programme, die un­ter­schied­li­che Spei­cher­for­ma­te für Dateien oder diverse Sortier- und Such­funk­tio­nen anbieten, auf Strategie-Ent­wurfs­mus­ter zurück. Auch im Bereich der Da­ten­kom­pres­si­on kommen Programme zum Einsatz, die auf der Basis des Ent­wurfs­mus­ters un­ter­schied­li­che Kom­pres­si­ons­al­go­rith­men im­ple­men­tie­ren. Sie können dadurch z. B. variabel Videos in ein ge­wünsch­tes platz­spa­ren­des Da­tei­for­mat kon­ver­tie­ren oder kom­pri­mier­te Ar­chiv­da­tei­en (z. B. ZIP- oder RAR-Dateien) mit spe­zi­el­len Ent­pa­cker­stra­te­gien wieder in den Ur­sprungs­zu­stand zu­rück­ver­set­zen. Ein anderes Beispiel wäre die Spei­che­rung eines Dokuments oder einer Grafik in ver­schie­de­nen Da­tei­for­ma­ten.

Das Design Pattern ist zudem an der Ent­wick­lung und Im­ple­men­tie­rung von Spie­le­soft­ware beteiligt, die z. B. zur Laufzeit flexibel auf wech­seln­de Spiel­si­tua­tio­nen reagieren muss. Un­ter­schied­li­che Cha­rak­te­re, spezielle Aus­rüs­tun­gen, Ver­hal­tens­mus­ter von Figuren oder ver­schie­de­ne Moves (besondere Be­we­gun­gen einer Spiel­fi­gur) können in Form von Con­cre­teStra­te­gies hin­ter­legt werden.

Ein weiteres Ein­satz­ge­biet von Strategy Patterns ist Steu­er­soft­ware. Durch den Austausch von Con­cre­teStra­te­gies können Be­rech­nungs­sät­ze pro­blem­los an Be­rufs­grup­pen, Länder und Regionen angepasst werden kann. Des Weiteren nutzen Programme, die Daten in ver­schie­de­ne Gra­fik­for­ma­te über­set­zen (z. B. als Linien-, Kreis- oder Säu­len­dia­gramm), Strategy Patterns.

Spe­zi­el­le­re An­wen­dun­gen von Strategy Patterns finden sich in der Java Stan­dard­bi­blio­thek (Java API) und bei Java GUI-Toolkits (z. B. AWT, Swing und SWT), die bei der Ent­wick­lung und Erzeugung von gra­fi­schen Be­nut­zer­schnitt­stel­len einen Layout-Manager nutzen. Dieser kann bei der Interface-Ent­wick­lung un­ter­schied­li­che Stra­te­gien für die Anordnung von Kom­po­nen­ten im­ple­men­tie­ren. Weitere An­wen­dun­gen von Strategy Design Patterns finden sich bei Da­ten­bank­sys­te­men, Ge­rä­te­trei­bern und Ser­ver­pro­gram­men.

Die wich­tigs­ten Ei­gen­schaf­ten des Strategy Patterns im Überblick

Das Strategie-Ent­wurfs­mus­ter zeichnet sich im um­fang­rei­chen Spektrum der Design Patterns durch folgende Ei­gen­schaf­ten aus:

  • ver­hal­tens­ori­en­tiert (Ver­hal­tens­wei­sen und -än­de­run­gen werden leichter pro­gram­mier- und im­ple­men­tier­bar, auch zur Laufzeit eines Programms sind Än­de­run­gen möglich)
  • ef­fi­zi­enz­ori­en­tiert (Aus­la­ge­run­gen ver­ein­fa­chen und op­ti­mie­ren Code und dessen Pflege)
  • zu­kunfts­ori­en­tiert (Än­de­run­gen und Op­ti­mie­run­gen sind auch mittel- und lang­fris­tig leicht zu rea­li­sie­ren)
  • zielt auf Er­wei­ter­bar­keit (be­güns­tigt durch die modulare Anlage und die Un­ab­hän­gig­keit von Objekten und Klassen)
  • zielt auf Wie­der­ver­wend­bar­keit (z. B. Mehr­fach­nut­zung von Stra­te­gien)
  • zielt auf op­ti­mier­te Be­dien­bar­keit, Steu­er­bar­keit und Kon­fi­gu­rier­bar­keit von Software
  • erfordert gründ­li­che kon­zep­tio­nel­le Vor­über­le­gun­gen (was kann wie an welchen Stellen in Strategie-Klassen aus­ge­la­gert werden)
Fazit

Strategy Patterns er­mög­li­chen in der ob­jekt­ori­en­tie­ren Pro­gram­mie­rung mit maß­ge­schnei­der­ten Pro­blem­lö­sun­gen eine ef­fi­zi­en­te und wirt­schaft­li­che Software-Ent­wick­lung. Schon in der Ent­wurfs­pha­se werden mög­li­cher­wei­se an­ste­hen­de Ver­än­de­run­gen und Ver­bes­se­run­gen optimal vor­be­rei­tet. Das auf Va­ria­bi­li­tät und Dynamik aus­ge­rich­te­te System kann insgesamt besser gesteuert und kon­trol­liert werden. Fehler und Un­ge­reimt­hei­ten werden schneller behoben. Durch wie­der­ver­wend­ba­re und aus­tausch­ba­re Kom­po­nen­ten werden besonders in komplexen Projekten mit lang­fris­ti­ger Per­spek­ti­ve Ent­wick­lungs­kos­ten ein­ge­spart. Al­ler­dings gilt es, das richtige Maß zu finden. Nicht selten werden Ent­wurfs­mus­ter entweder zu sparsam oder zu häufig ein­ge­setzt.

Zum Hauptmenü