Einführung in die Policies/das Strategy Pattern
-
In letzter Zeit wird immer mehr in den codes mit neuen Techniken gearbeitet, die für neueinsteiger in C++ schwer zu verstehen sind. Eine dieser Techniken ist das Strategy Pattern, hier im Forum häufig nur Policy genannt.
(diese Einführung richtet sich an leute, die schon mit vererbung und templates arbeiten können, vorschläge sind gern gesehen. Ich hatte schon länger vor, sowas zu schreiben, und häng mich jetzt deshalb an virtuals Listen Einführung an)
Was ist das Strategy Pattern?
Das Strategy Pattern ist eine Technik, die Aufgaben einer Klasse auslagert.
Diese Auslagerung ermöglicht es, verschiedene Lösungen für bestimmte Probleme zu implementieren und je nach Problemvariation, die richtige Version herauszusuchen, ohne gleich die komplette Klasse ändern zu müssen. Diese Auslagerung ist insofern wichtig, da der Code dadurch weniger speziell und somit wiederverwendbarer ist. Eine bestehende Klasse kann einfach durch das schreiben neuer policies auf ein spezielles Problem optimiert werden, ohne sie selber verändern zu müssen. Dies verbessert die Übersichtlichkeit und verringert die Fehleranfälligkeit des codes.Wann benutzt man das Strategy Pattern?
Das Strategy Pattern kommt dann zum Einsatz, wo Code Situationsabhängig ist.Beispiel:
nehmen wir folgende Klasse:class TextFormater{ private: void insertLineBreaks(std::string&); //... public: format(std::string& text){ insertLineBreaks(text); //... } };
Jeder weis, dass es verschiedene Wege gibt, einen Text in mehrere Zeilen zu schreiben
man kann:
Am Ende der Zeile s ofort brechen
man kann:
Am Ende der nächs ten Silbe brechen
man kann:
Am Ende des nächsten Wortes brechen
schaut man sich den Code von oben an, so sieht man, dass diese Klasse die Aufgabe des verwaltens der verschiedenen Algorithmen so nicht gerecht werden kann. Man müsste also einen weg finden, die Funktion ausserhalb der Klasse auszulagern.
Wie benutzt man das Strategy Pattern
Es gibt mehrere Wege, das Strategy Pattern zu implementieren:Dynamisch:
class LineBreaker{ public: virtual void insertLineBreaks(std::string&); }; class TextFormater{ private: LineBreaker* breaker; //... public: format(std::string& text){ breaker->insertLineBreaks(text); //... } void setLineBreaker(LineBreaker* newBreaker){ breaker=newBreaker; } }; //benutzung struct SimpleLineBreaker:public LineBreaker{ void insertLineBreaks(std::string&){...} }; Textformater t; LineBreaker* breaker=new SimpleLineBreaker; t.setLineBreaker(breaker); t.format(someInput); delete breaker;
Vorteil: Während der Laufzeit des Programms kann der Algorithmus geändert werden, variablen können benutzt werden
Nachteil: jedes mal ein virtual call
Statisch:
bei der statischen implementation gibt es mehrere Ansätze, die sich aber von der Grundstruktur sehr ähnlich sind:template<class Breaker> class TextFormater{ //... public: format(std::string& text){ Breaker::insertLineBreaks(text); //... } }; //benutzung struct SimpleLineBreaker{ static void insertLineBreaks(std::string&){...} }; TextFormater<SimpleLineBreaker> t; t.format(someInput);
Vorteil: keine virtual call, keine vererbung nötig, einfach zu handhaben
Nachteil: statisch, also nicht ohne weiteres zur Laufzeit änderbar, in der policy können keine variablen benutzt werden
Wie funktioniert das?
der übergebene typ im template parameter stellt die Klasse dar, die die statische funktion insertLineBreaks implementiert. diese Funktion wird nun immer dann aufgerufen, wenn format aufgerufen wird.template<class Breaker> class TextFormater{ Breaker breaker; //... public: TextFormater(const Breaker& breaker=Breaker()):breaker(breaker){} format(std::string& text){ breaker.insertLineBreaks(text); //... } }; //benutzung struct SimpleLineBreaker{ void insertLineBreaks(std::string&){...} }; TextFormater<SimpleLineBreaker> t; //oder TextFormater<SimpleLineBreaker> u(SimpleLineBreaker(/*...*/)); t.format(someInput);
Vorteil: variablen sind möglich, der user kann die Policy initialisieren
Nachteil: wieder statisch, der venutzer hat so kaum zugriff auf die Policy selber
Wie funktioniert das?
der template parameter ist der typ des objekts, welches die Klasse dann "aufnimmt". der User hat hierbei die Möglichkeit, direkt im ctor die Policy zu initialisieren.template<class Breaker> class TextFormater:public Breaker{ //... public: TextFormater(const Breaker& breaker=Breaker()):Breaker(breaker){} format(std::string& text){ Breaker::insertLineBreaks(text); //... } }; //benutzung struct SimpleLineBreaker{ protected: void insertLineBreaks(std::string&){...} }; TextFormater<SimpleLineBreaker> t; //oder TextFormater<SimpleLineBreaker> u(SimpleLineBreaker(/*...*/)); t.format(someInput);
Vorteil:sehr flexibel, sogut wie alles ist möglich
Nachteil: es ist immer vererbung mit ihren nachteilen im spielWie funktioniert das?
Der template parameter ist hierbei direkt basisklasse, und genießt deshalb alle rechte die er nur haben kann. insertLineBreaks ist protected, da die funktion sonst durch die public vererbung für jeden frei zugänglich wäre.Was sollte man nun verwenden?
Klar, die dynamische Möglichkeit ist schön, da man so während der Laufzeit einen anderen Algorithmus ändern kann.bei den template möglichkeiten ist man natürlich am eingeschränktesten, hier geht normalerweise nur die funktion selber.
die dritte Möglichkeit ist in vielen Fällen zu mächtig, man braucht sie nur sehr selten.
die zweite Möglichkeit ist ein gesundes Mittelmaß und wird am häufigsten verwendet.
Die Dritte Möglichkeit kann aber die erste problemlos ersetzen, weshalb sie in Fällen, in denen die Policy dynamisch sein kann verwendet werden sollte:
class LineBreaker{ public: virtual void insertLineBreaks(std::string&); }; class DynamicLineBreaker{ private: LineBreaker* breaker; protected: void insertLineBreaks(std::string& text){ breaker->insertLineBreaks(text); } public: void setLineBreaker(LineBreaker* newBreaker){ breaker=newBreaker; } }; template<class Breaker> class TextFormater:public Breaker{ //... public: TextFormater(const Breaker& breaker=Breaker()):Breaker(breaker){} format(std::string& text){ Breaker::insertLineBreaks(text); //... } }; struct SimpleLineBreaker:public LineBreaker{ void insertLineBreaks(std::string&){...} }; Textformater<DynamicLineBreaker> t; LineBreaker* breaker=new SimpleLineBreaker; t.setLineBreaker(breaker); t.format(someInput); delete breaker;
Ich hoffe, dass ich so eine kleine Einführung in die Welt der Policies geben konnte, und ich hoffe auch, dass der code so sinnvoll und vorallem richtig ist *drei Kreuze macht*. Ansosnten wird mich sicher jemand drauf hinweisen
-
Vielen Dank für den Beitrag! Mal was anderes hier im Forum.
-
Netter Beitrag!
Trotz allem ein kleiner Einwand meinerseits:
otze schrieb:
In letzter Zeit wird immer mehr in den codes mit neuen Techniken gearbeitet, die für neueinsteiger in C++ schwer zu verstehen sind. Eine dieser Techniken ist das Strategy Pattern, hier im Forum häufig nur Policy genannt.
Ist das wirklich so?
Ich weiß, ist Haarspalterei, Überschneidungen nicht ausgeschlossen, aber für mich (IMAO, sozusagen) sind Policies (oder Policy-Based Design) und das Strategy Pattern schon zwei verschiedene Dinge.
Für mich wären die dynamische Variante und auch statisch arabisch 3, die "policy-based Stratety" (
), dem Strategy Pattern zuzuordnen, statisch 1 und 2 den Policies.
Grob gesagt geht's für mich bei den Policies um die von dir angesprochene Wiederverwendbarkeit, oder auch vordergründig die Konfigurierbarkeit.
Dein "Text"-Beispiel hingegen schreit geradezu nach dem Strategy Pattern, (obwohl denke ich eher der TextFormatter selber eine Strategy wäre,) wo es eher darum geht für eine Aufgabe (oder Aufgabenbereich) ein und desselben Typs (im Gegensatz zu x verschiedenen Typen TextFormater<BreakingPolicy>) verschiedene Strategien anwenden zu können.
Mit der Bitte um Aufklärung/andere Meinungen
-
Dazu sollte man allerdings sagen, dass die strategy pattern nicht unbedingt generisch implementiert werden muss. Es ist ebenfalls denkbar, eine Klassenhierarchie aufzubauen und das konkrete Objekt zur Laufzeit dem Konstruktor zu geben. Zum Beispiel:
class output_traits { public: virtual void operator()(std::string const &) = 0; }; class stream_output_traits : public output_traits { public: stream_output_traits(std::ostream &out) : out_(out) { } virtual void operator()(std::string const &msg) { out_ << msg << std::flush; } private: std::ostream &out_; }; class gl_output_traits : public output_traits { public: virtual void operator()(std::string &msg) { // Irgendwas lustiges. Öffne ein Fenster und lasse den Text darin rotieren oder so. } }; class class_that_uses_output_traits { public: class_that_uses_output_traits(output_traits &output) : output_(output) { } void do_something() { output_("Hello, World!"); } private: output_traits &output_; } // ... stream_output_traits stream_out(std::cout); gl_output_traits gl_out; class_that_uses_output_traits foo(stream_out); class_that_uses_output_traits bar(gl_out); foo.do_something(); bar.do_something();
Auf die Art kann man traits auch zur Laufzeit übergeben. Das ist zum Beispiel für Plugin-Architekturen aller Art interessant, oder auch vor dem Hintergrund der closed-source-Entwicklung, die ihre Bibliothek möglicherweise nicht im Quelltext ausliefern will und templates dementsprechend eher vorbehalten gegenübersteht.
-
OOps...sollte nächstes mal gründlicher lesen, steht ja schon da. Naja - gibts halt ein weiteres Anschauungsbeispiel.
-
0xdeadbeef schrieb:
Dazu sollte man allerdings sagen, dass die strategy pattern nicht unbedingt generisch implementiert werden muss. Es ist ebenfalls denkbar, eine Klassenhierarchie aufzubauen und das konkrete Objekt zur Laufzeit dem Konstruktor zu geben.
Das war glaube ich mit dem Punkt dynamisch gemeint
-
erstmal danke für die netten worte
finix schrieb:
Trotz allem ein kleiner Einwand meinerseits:
otze schrieb:
In letzter Zeit wird immer mehr in den codes mit neuen Techniken gearbeitet, die für neueinsteiger in C++ schwer zu verstehen sind. Eine dieser Techniken ist das Strategy Pattern, hier im Forum häufig nur Policy genannt.
Ist das wirklich so?
ja, das ist wirklich so. Ich hab grad nochmal in Entwurfsmuster reingeschaut, da steht:
Strategy Also Known As: Policy
-
Super Beitrag, danke! Wieder ein wenig was neues dazu gelernt
-
otze schrieb:
erstmal danke für die netten worte
finix schrieb:
Trotz allem ein kleiner Einwand meinerseits:
otze schrieb:
In letzter Zeit wird immer mehr in den codes mit neuen Techniken gearbeitet, die für neueinsteiger in C++ schwer zu verstehen sind. Eine dieser Techniken ist das Strategy Pattern, hier im Forum häufig nur Policy genannt.
Ist das wirklich so?
ja, das ist wirklich so. Ich hab grad nochmal in Entwurfsmuster reingeschaut, da steht:
Strategy Also Known As: Policy
Ja, so gesehen natürlich schon. Meinte allerdings auch eher die Unterscheidung (?)
Strategy Pattern <-> Policy Based Class Design
was für mich zumindest in der Intention, dem Nutzen, der Verwendungsmöglichkeiten ein Unterschied ist.
-
Hi otze,
schöner Text. Ich denke wenn wir den alle gemeinsam noch etwas rundschleifen, soltte das Ganze dann auch in die FAQs.Hier mal meine 0.02 Euro:
otze schrieb:
Was ist das Strategy Pattern?
Das Strategy Pattern ist eine Technik, die Aufgaben einer Klasse auslagert.
Diese Auslagerung ermöglicht es, verschiedene Lösungen für bestimmte Probleme zu implementieren und je nach Problemvariation, die richtige Version herauszusuchen, ohne gleich die komplette Klasse ändern zu müssen.Wenn ich das Strategy-Pattern nicht schon kennen würde, wäre meine Reaktion auf diese doch sehr abstrakte Beschreibung: Bahnhof?
Imo ist diese Beschreibung noch weniger verständlich als der Original-Intent-Text des GoF-Buchs.otze schrieb:
Diese Auslagerung ist insofern wichtig, da der Code dadurch weniger speziell und somit wiederverwendbarer ist.
Das primäre Ziel von Strategy ist imo nicht bessere Wiederverwendbarkeit sondern (so wie bei den meisten GoF-Patterns) Reduktion von Komplexität. Komplexität hervorgerufen durch komplizierte und vielfältige Bedingungen.
Strategy hilft dann, wenn die vielen Bedingungen hauptsächlich deshalb existieren um viele Variationen *eines* Basis-Algortihmus' zu implementieren.
In diesem Fall ziehe ich die Variationen aus der Kontext-Klasse raus und implementiere das Ganze als Familie von Algorithmen in einer eigenen Hierarchie. Dadurch wird zum einen die Kontext-Klasse einfacher und zum anderen sind die Variaten des Algos besser zu erkennen, da jede durch einen eigenen Typ repräsentiert wird.Wann benutzt man das Strategy Pattern?
Das Strategy Pattern kommt dann zum Einsatz, wo Code Situationsabhängig ist.Das ist zu weitläufig. Nicht überall wo Code situationsabhängig ist, ist ein Strategy-Objekt die Lösung. Wenn ich z.B. bereits eine Klassenhierarchie habe, dann löse ich Situationsabhängigkeit eher mit Polymorphismus.
[...]snip gutes Beispeil[...]
schaut man sich den Code von oben an, so sieht man, dass diese Klasse die Aufgabe des verwaltens der verschiedenen Algorithmen so nicht gerecht werden kann. Man müsste also einen weg finden, die Funktion ausserhalb der Klasse auszulagern.Klar könnte sie:
class TextFormater { public: enum LineBreakAt {END_OF_LINE, END_OF_SYLLABLE, END_OF_WORD}; ... void insertLineBreaks(std::string& s) { switch(lbStrat_) { case END_OF_LINE: insertEolLBs(s); break; ... } private: void insertEolLBs(std::string&); ... };
Diese Implementation ist ja nicht notwendigerweise schlecht. Sie kann für eine konkrete Situation aber zu unflexibel sein. Wenn häufig neue Algos hinzukommen muss ich z.B. ständig die Klasse ändern. Und damit im Zweifelsfall auch Clients belästigen, die sich für den neuen Algo gar nicht interessieren. Außerdem wird die Klasse dadurch über die Zeit zum Monster.
Strategy ist hier eine schöne Lösung. Du solltest aber neben den Vorteilen von Strategy auch die Nachteile erwähnen (jedes Pattern hat Konsequenzen und die sind nicht immer nur gut). Strategy macht eine Klasse z.B. häufig schwerer instanziierbar, da durch Strategy aufeinmal Konfigurationswissen nötig wird (irgendwer muss das passende Strategy-Objekt instanziieren).
Und was ist, wenn der Algo Daten der Kontext-Klasse braucht? In diesem Fall verkompliziert sich in der Regel die Implementation (im Gegensatz zum "alles in einer Klasse-Ansatz").Wie benutzt man das Strategy Pattern
Es gibt mehrere Wege, das Strategy Pattern zu implementieren:Dynamisch:
[...]Das ist die klassische OOP-Implementation von Strategy die auf Delegation basiert. Und imo die Variante, die in den meisten Situationen am passensten ist.
Vorteil: Während der Laufzeit des Programms kann der Algorithmus geändert werden, variablen können benutzt werden
+ Der Typ der Kontext-Klasse bleibt unverändert. Sprich: Die Umstellung auf Strategy ist für Clients transparent.
Nachteil: jedes mal ein virtual call
Das würde ich im Allgemeinen Fall nicht als Nachteil ansehen. Denn im allgemeinen Fall ist der Algo locker so kompliziert, dass seine Laufzeit deutlich größer ist als die Zeit für einen virtuellen Aufruf. Man verwendet das Strategy-Pattern ja nicht so wie Policies in Policy-Based-Design.
Statisch:
bei der statischen implementation gibt es mehrere Ansätze, die sich aber von der Grundstruktur sehr ähnlich sind:[...]
2)
[...]
3)
[...]Diese Variante ist imo nur sinnvoll, wenn der Code drumherum eh schon mit Tempates arbeitet. Da hier der konkrete Algorithmus teil des statischen Typs der Kontext-Klasse ist, ist dieser Ansatz ansonsten völlig unbrauchbar (da für Clients nicht transparent).
Das ist so eine Implementation, die imo mehr in die Ecke von Policy-Based-Design passt, als in die Ecke Strategy-Pattern.die dritte Möglichkeit ist in vielen Fällen zu mächtig, man braucht sie nur sehr selten.
In wie fern ist die dritte Möglichkeit mächtiger als 1) oder 2)? Imo ist das ein reines Implementationsdetail. Das was 3) kann, kann ich auch mit 2) erreichen (mal von so Spielchen wie Policy-Injizierte-Virtualität abgesehen).
die zweite Möglichkeit ist ein gesundes Mittelmaß und wird am häufigsten verwendet.
Für was? Das ist Kontextabhängig. Klar in der generischen Programmierung bzw. beim Policy-Based-Design à la MCPPD realisiert man variable Algorithmen so. Aber in einem OO-Programm, einem OO-Framework o.Ä. implementiere ich Strategy eindeutig über eine Klassenhierarchie + Delegation.
Die Dritte Möglichkeit kann aber die erste problemlos ersetzen, weshalb sie in Fällen, in denen die Policy dynamisch sein kann verwendet werden sollte:
Wenn dein Ziel Komplexitätsreduktion ist, dann scheint mir die dritte Möglichkeit nur dann die Beste zu sein, wenn ich all die Flexibilität auch brauche. Nicht immer ist das mächtigste Werkzeug das Beste.
-
erstmal vorneweg: ich finde es schön, dass jetzt mein Text auch mal negativ kritisiert wird. mir war klar, dass es da bestimmt noch jede Menge zu Feilen gibt.
Wenn ich das Strategy-Pattern nicht schon kennen würde, wäre meine Reaktion auf diese doch sehr abstrakte Beschreibung: Bahnhof?
Imo ist diese Beschreibung noch weniger verständlich als der Original-Intent-Text des GoF-Buchs.Wenn ich mir den Text heute nochmal anschaue, geb ich dir vollkommen recht. Vielleicht sollte ich den Text doch noch ein bischen auskleiden und nicht so kurz fassen, dann wird das denk ich mal sofort klarer.
gut, ich überleg mir dann mal eine verbesserung:
Was ist das Strategy Pattern?
Viele Aufgaben einer Klasse sind stark von ihrem Umfeld abhängig, kaum eine Situation gleicht der anderen, und der Versuch, in der Klasse möglichst viele Fälle abzudecken führt zu einer enormen Steigerung der Komplexität.
Die Chancen, dass solch komplexe Klassen Quasi unwartbar werden ist sehr hoch, weshalb es sich als gut herausgestellt hat, dass man kontextabhängige Aufgaben einer Klasse direkt nach ausserhalb verlagert.
Ein Pattern welches für diese Aufgabe entwickelt wurde, ist das Strategy Pattern(oftmals auch nur "Policy" genannt).Klassen, die das Strategy Pattern implementieren, können einfach durch das schreiben neuer policies auf ein spezielles Problem optimiert werden, ohne sie selber verändern zu müssen. Dies verbessert die Übersichtlichkeit und verringert die Fehleranfälligkeit des Codes.
Dies hat auch zur Folge, dass man sich bei der Erstellung der Klasse nicht mehr auf alle Eventualitäten vorbereiten muss, da schon durch die Wahl des Algorithmus für die jeweilige Aufgabe viele Fälle einfach von vornherein ausschließt.
(ich hoffe, dass ist so jetzt besser)
Nun zum zweiten teil:Wann benutzt man das Strategy Pattern?
Das Strategy Pattern kann dann zum Einsatz kommen, wenn Code Situationsabhängig ist. Hierbei sollte man aber auch auf das Globale Umfeld des Programms achten. Wenn im Programm selber Hauptsächlich Polymorphismus verwendet wird, kann das Strategy Pattern die Übersicht stören und den Code schwerer verständlich machen.Auch sollte man sich im Klaren sein, dass das Strategy Pattern Nachteile besitzt. Policy Klassen sind oftmals schwerer zu initialisieren, da man nicht nur die Klasse selber sondern in einigen Fällen auch die Policies initialisieren muss.
Ein weiterer Nachteil ist, dass ein Policy basiertes Design oftmals in der implementation viel Zeit verschlingt, da man sich im Vorfeld Gedanken darüber machen muss, was nun wie aus der Klasse ausgelagert werden muss, und was für Daten die Policys dann zur verfügung gestellt bekommen müssen, um effektiv zu arbeiten.
Zuletzt ist noch anzumerken, dass Klassen die das Strategy Pattern implementieren viel ausführlicher Dokumentiert werden müssen, damit der user nicht nur den sinn der einzelnen Policys erkennt, sondern auch mit den bereits vorhanden Lösungen der einzelnen probleme vertraut gemacht wird.
In wie fern ist die dritte Möglichkeit mächtiger als 1) oder 2)? Imo ist das ein reines Implementationsdetail. Das was 3) kann, kann ich auch mit 2) erreichen (mal von so Spielchen wie Policy-Injizierte-Virtualität abgesehen).
- ist schon verdammt mächtig, da es das interface der Klasse verändern kann. Ein weiteres schönes Beispiel für sowas ist die abstrakte Fabrik. Eine Policy die das erstellen der Objekte übernimmt, könnte zb durch Klonen eines Prototyps neue Objekte erstellen. hierfür muss die Policy aber die Möglichkeit bieten, dass Prototypen hinzugefügt oder ausgetauscht werden können, was 2) zb nicht so ohne weiteres leisten kann. durch die vererbung aber ist diese aufgabe für 3) sehr einfach zu lösen, da sie die entsprechende Methode einfach vererbt.
Diese Variante ist imo nur sinnvoll, wenn der Code drumherum eh schon mit Tempates arbeitet. Da hier der konkrete Algorithmus teil des statischen Typs der Kontext-Klasse ist, ist dieser Ansatz ansonsten völlig unbrauchbar (da für Clients nicht transparent).
Dies gilt aber nur, solange die typveränderung nicht hinter einem basisklassenzeiger versteckt wird.
Das ist so eine Implementation, die imo mehr in die Ecke von Policy-Based-Design passt, als in die Ecke Strategy-Pattern.
Jein. In vielen Sprachen wird das Strategy Pattern über Polymorphie implementiert, in C++ hat man aber dank der templates die Möglichkeit, auf polymorphie zu verzichten und einen anderen Weg einzuschlagen. Policy-Based-Design würd ich deshalb eher als einen Sonderfall des Strategy Pattern beschreiben, und nicht als was völlig anderes. Beide Techniken verhalten sich,solange die algorithmenzusammenstellung statisch ist, eh equivalent(wenn auch auf anderen Ebenen).
(falls ich hier einem Irrtum erliege, bitte mit einem Zaunpfahl feste schlagen
)
Wenn dein Ziel Komplexitätsreduktion ist, dann scheint mir die dritte Möglichkeit nur dann die Beste zu sein, wenn ich all die Flexibilität auch brauche. Nicht immer ist das mächtigste Werkzeug das Beste.
das sagte ich doch auch :).
(klar, wenn schon von vornherein bekannt ist, dass die policy nicht statisch sein kann, dann sollte man die dynamische Möglichkeit favorisieren, aber wenn man weis, dass die Klasse immer wieder in komplett anderen kontexten benutzt wird, wieso dann nicht 3)?)
-
Warum lagert man Funktionalität aus der Klasse aus anstatt einfach eine weitere Methode der Klasse spendieren die den Text anders formatiert.
Somit bleibt die Klasse übersichtlich und es ist nicht so umständlich.
Ich sehe den Vorteil dieses Pattern nicht. Hat den Geschmack von 'von hinten durch die Brust ins Auge'.
Pattern sind ja was feines aber bei manchen muss man sich schon fragen ob sie die Sache komplizierter machen als nötig. Ich nenne das immer Überabstraktion.
-
@otze
Du solltest dich imo auf ein Wort festlegen. Entweder Policy oder Strategy. Du kannst ja am Anfang schreiben, dass beide Worte synonym verwendet werden, aber das ständige mischen von Policy und Strategy ist imo verwirrend.otze schrieb:
Wann benutzt man das Strategy Pattern?
Das Strategy Pattern kann dann zum Einsatz kommen, wenn Code Situationsabhängig ist. Hierbei sollte man aber auch auf das Globale Umfeld des Programms achten. Wenn im Programm selber Hauptsächlich Polymorphismus verwendet wird, kann das Strategy Pattern die Übersicht stören und den Code schwerer verständlich machen.Dieser Absatz sagt mir als Leser gar nicht. Was bedeutet es "auf das Globale Umfeld des Programms" zu achten?
Wenn im Programm selber Hauptsächlich Polymorphismus verwendet wird, kann das Strategy Pattern die Übersicht stören und den Code schwerer verständlich machen.
In wie weit? Das Strategy-Pattern basiert in seiner Standard-Implementation doch selbst auf Polymorphie.
Der Punkt ist doch eher, dass du Komplexität die aus switch-on-types resultiert auf verschiedene Art und Weise reduzieren kann. Wenn du bereits eine Klassenhierarchie hast, dann reicht es häufig einfacher die Typswitcherei durch Polymorphie zu ersetzen (Shape-Beispiel mit verschiedenen Klasse aber ohne virtuelle draw-Methode -> erstelle virtuelle draw-Methode).
Hast du noch keine Hierarchie dafür aber nur einen Typ-Code auf dem geswitched wird, hilft es oft diesen durch abgeleitete Klassen zu ersetzen und dann wiederum Polymorphie einzusetzen (Shape-Beispiel mit nur einer Shape-Klasse dafür mit enum Type -> erstelle abgeleitete Klassen für jedes Element aus Type).
Hast du hingegen keinen Typ-Code (bzw. ist dieser Teil einer Klasse und nach außen hin nicht sichtbar) oder soll das Verhalten zur Laufzeit variierbar sein (erst arbeite ich mit Algo-Version A, dann mit B), dann macht Strategy sinn.Auch sollte man sich im Klaren sein, dass das Strategy Pattern Nachteile besitzt. Policy Klassen sind oftmals schwerer zu initialisieren, da man nicht nur die Klasse selber sondern in einigen Fällen auch die Policies initialisieren muss.
"in einigen Fällen" kannst du konkretisieren. Immer dann, wenn die Kontext-Klasse ihre Strategy nicht selbst instanziiert. Sprich: Wenn die Strategy nicht nur ein reines Implementationsdetail ist.
Ein weiterer Nachteil ist, dass ein Policy basiertes Design oftmals in der implementation viel Zeit verschlingt[...]
Das ist jetzt ein Kontextwechsel. Policy basiertes Design und Strategy-Pattern sind zwei völlig verschiedene Dinge. Policy basiertes Design verwendet in der Implementation Strategy, aber um Strategy zu verwenden musst du kein Policy basiertes Design betreiben. Policy basiertes Design ist imo auch viel eingeschränkter nutzbar als das Strategy-Pattern, da es nur für wenige Klassen sinn macht sie in viele orthogonale Policies zu zerlegen.
Ich würde mich an deiner Stelle also in dem Text auf eine Sache konzentrieren. Ansonsten wird es zu umfangreich.
otze schrieb:
HumeSikkins schrieb:
Das ist so eine Implementation, die imo mehr in die Ecke von Policy-Based-Design passt, als in die Ecke Strategy-Pattern.
Jein. In vielen Sprachen wird das Strategy Pattern über Polymorphie implementiert, in C++ hat man aber dank der templates die Möglichkeit, auf polymorphie zu verzichten und einen anderen Weg einzuschlagen.
In dem Moment wo der es für den Client transparent sein soll (bzw. er nicht selbst ein Template ist) kommst du auch in C++ nicht um Laufzeitpolymorphie drum rum. Ich sehe nicht ganz worauf du hinaus willst.
-
Warum lagert man Funktionalität aus der Klasse aus anstatt einfach eine weitere Methode der Klasse spendieren die den Text anders formatiert.
wenn du eine klasse mit mehr als 20 methoden der textformatierung benutzen willst...
obwohl...ist das noch übersichtlich?Ist es nicht vielleicht schon sehr umständlich jedes mal das interface der Klasse ändern zu müssen, wenn man eine andere Funktionalität braucht?
interessanter wird dies ja bei sort algorithmen. Da reicht nicht einer und alle sind zufrieden, der "beste" algorithmus ist vom typ des containers und vom typ der daten abhängig, dabei muss man immer geschwindigkeit gegen speicherverbrauch abwägen.
Auch sind manche algorithmen besser bei kleineren datenmengen, andere spielen ihre vorteile erst bei richtig großen Mengen an daten aus.wenn du für jeden dieser fälle eine eigene methode bereitstellen willst, dann gute nacht!
-
sepp24 schrieb:
Warum lagert man Funktionalität aus der Klasse aus anstatt einfach eine weitere Methode der Klasse spendieren die den Text anders formatiert.
Somit bleibt die Klasse übersichtlich und es ist nicht so umständlich.
Wie ich in meinem ersten Beitrag bereits schrieb hat dieser Ansatz manchmal durchaus seine Vorteile. Er kann aber auch ganz schnell zum Problem werden. Nämlich genua dann, wenn die Zahl der Variationen sehr groß ist. In diesem Fall wird deine Klasse mit der Zeit nämlich A) zu einem Monster (wartbarkeit, testbarkeit und benutzbarkeit gehen den Bach runter) und
zu einem "major pain in the butt" für alle Clients die nicht an allen Variationen interessiert sind.
-
so, um das jetzt mal abschließen zu können:
Policy basiertes Design und Strategy-Pattern sind zwei völlig verschiedene Dinge. Policy basiertes Design verwendet in der Implementation Strategy, aber um Strategy zu verwenden musst du kein Policy basiertes Design betreiben. Policy basiertes Design ist imo auch viel eingeschränkter nutzbar als das Strategy-Pattern, da es nur für wenige Klassen sinn macht sie in viele orthogonale Policies zu zerlegen.
Wo genau liegt denn der Unterschied zwischen Policy basiertem Design und dem Strategy Pattern? Wenn ich den Artikel irgendwie nochmal umschreiben soll, muss ich verstehen, was ich falsch verstanden hab.
Bisher versteh ich das so, dass Policy based Design und die Implementation des Strategy Patterns über Polimorphie 2 Kehrseiten ein und derselben Medaille sind, und somit irgendwie zusammen gehören.
klar ist mir, dass Policy based Design den Typ der Klasse ändert. In einer template umgebung macht dies aber wie du das selber gesagt hast, nichts aus. Wahrscheinlich ist man da mit templates als implementation plötzlich viel besser dran. in einem OO Design sieht das natürlich anders aus, da funktioniert es mit vererbung besser.
Imho ist es falsch, Policy basiertes Design vom Strategy Pattern so zu trennen, immerhin verfolgen sie ein und dasselbe ziel, nur auf leicht verschobenen Ebenen. Oder kommts im endeffekt nur auf die Ebenen an?
//edit frage am rande: wie sieht es mit functoren aus?
sind functoren auch eine Möglichkeit das Strategy Pattern zu implementieren?
-
Wo genau liegt denn der Unterschied zwischen Policy basiertem Design und dem Strategy Pattern?
Meiner Meinung nach in der Generizität. Strategy ist ein allgemeines Design-Pattern und hat damit viele verschiedene mögliche Implementationen. PBD hingegen verwendet in seiner Implementation eine bestimmte Ausprägung des Strategy-Patterns, nennen wir es mal Static Strategy.
Ich sehe außerdem noch einen Unterschied in der Zielsetzung. Beim PBD versucht man Klassen in möglichst viele orthogonale Policies zu zerlegen. Das Wunschziel ist, dass die Host-Klasse (Kontext-Klasse) am Ende nur noch ein Policy-Container ist (die Host-Klasse spezifiziert die Policy zur Kommunikation der Policies
). Das Strategy-Pattern hat keine solche Zielsetzung. Eher könnte man sagen, dass PBD das Strategy-Pattern iterativ immer wieder anwendet bis von der Kontext-Klasse nichts mehr übrigbleibt.
Auch scheint mir dieses static-vs-dynamic-Ding einen wichtigen Unterschied zu machen. Ich denke Leuete die das klassische Strategy-Pattern kennen, schätzen an diesem gerade die Tatsache, dass man den ausgelagerten Algo zur Laufzeit auswechseln kann. Ziel: Laufzeit-Flexibilität.
PBD zielt eher in Richtung generative Programmierung -> Flexibilität durch Code-Generation zur Compilezeit. Ich definiere viele einfache orthogonale Komponenten und lasse mir diese durch den Compiler zu einem sinnvollen Ganzen zusammensetzen.
Das mag alles etwas schwammig sein. Vom Gefühl her ist Strategy für mich einfach weitläufiger als PBD. Strategy ist für mich eine allgemeingültige Denk-Technik. PBD eher ein C++ spezifischer Implementationstrick.
Hm, aber wie und wo lässt sich jetzt sowas wie CORBAs Portable Object Adapter einordnen? Der ist eindeutig "policy-based" dafür aber weder eine Ausprägung des klassischen Strategy-Patterns (zumindest aus Client-Sicht) noch im Alexandrescu'schen Sinne PBD-mäßig implementiert.
//edit frage am rande: wie sieht es mit functoren aus?
sind functoren auch eine Möglichkeit das Strategy Pattern zu implementieren?Nicht die Funktoren an sich aber die Funktionstemplates die gewisse Aufgaben an ein Funktor delegieren.