Stratégie (patron de conception) — Wikipédia

En génie logiciel, le patron stratégie est un patron de conception (design pattern) de type comportemental grâce auquel des algorithmes peuvent être sélectionnés à la volée au cours du temps d'exécution selon certaines conditions.

Le patron de conception stratégie est utile pour des situations où il est nécessaire de permuter dynamiquement les algorithmes utilisés dans une application. Le patron stratégie est prévu pour fournir le moyen de définir une famille d'algorithmes, encapsuler chacun d'eux en tant qu'objet, et les rendre interchangeables. Ce patron laisse les algorithmes changer indépendamment des clients qui les emploient.

Utilisation[modifier | modifier le code]

Dès lors qu'un objet peut effectuer plusieurs traitements différents, dépendant d'une variable ou d'un état[1].

Structure[modifier | modifier le code]

Patron Stratégie en UML
Patron Stratégie en LePUS3 (legend)

Exemple en C++[modifier | modifier le code]

#include <iostream> #include <memory>  // IStrategie est l’interface permettant d’exécuter un algorithme class IStrategie { public:     void execute() { process(); } // NVI      virtual ~IStrategie() = default; // Héritage, donc destructeur public virtuel  private:     virtual void process() = 0; // IStrategie::process() est une fonction virtuelle pure                                 // et de ce fait IStrategie est une classe abstraite                                 // autrement dit une classe qui ne peut être instanciée };  class AlgorithmeA : public IStrategie { private:     // Chaque Algorithme redéfinit la façon de procéder     void process() override     {         std::cout << "Traitement A" << std::endl;     } };  class AlgorithmeB : public IStrategie { private:     void process() override     {         std::cout << "Traitement B" << std::endl;     } };  class AlgorithmeC : public IStrategie { private:     void process() override     {         std::cout << "Traitement C" << std::endl;     } };  // Contexte est la classe visible par le client. // Elle fait le lien entre les demandes du client et l’algorithme (ou les algorithmes) à utiliser. class Contexte final { private:     std::unique_ptr<IStrategie> strategie;  public:     Contexte(std::unique_ptr<IStrategie> new_strategie)         : strategie(std::move(new_strategie))     {}      void execute()     {         strategie->execute();     }      void setAlgorithme(std::unique_ptr<IStrategie> new_strategie)     {         strategie = std::move(new_strategie);     } };  int main() {     Contexte contexte(std::make_unique<AlgorithmeA>());     contexte.execute();     // Le contexte va effectuer le traitement A      contexte.setAlgorithme(std::make_unique<AlgorithmeB>());     contexte.execute();     // Le contexte va effectuer le traitement B      contexte.setAlgorithme(std::make_unique<AlgorithmeC>());     contexte.execute();     // Le contexte va effectuer le traitement C      return 0;  } 

Voir classe abstraite et fonction virtuelle pure.

Exemple en C#[modifier | modifier le code]

Des idées semblables amènent à une réalisation à l'aide d'interface.

L'objet qui doit avoir une stratégie adaptable à l'exécution implémente IStrategie : la même interface que d'autres objets. L'objet principal délègue l'exécution de la tâche à un autre objet membre qui implémente IStrategie.

L'objet membre étant déclaré dans la classe comme une interface, son implémentation importe peu, on peut donc changer de stratégie à l'exécution. Cette manière de faire se rapproche du Principe de l'injection de dépendance.

using System;  /// <summary> La manière dont le grand général guidera ses troupes</summary> interface IStrategie {     void MettreEnOeuvre(); }  /// <summary> Ce grand homme qui fera bientôt des choix décisifs </summary> class SeigneurDeLaGuerre {      /// <summary> une stratégie générique </summary>     IStrategie _strategie;      /// <summary> comment changer de stratégie </summary>     public IStrategie Strategie { set { _strategie = value; } }      /// <summary> délégation de la tâche </summary>     public void PrendreLaVille() {         _strategie.MettreEnOeuvre();     } }  class DéfoncerLePontLevisDeFace : IStrategie {     public void MettreEnOeuvre() {         Console.WriteLine("Prendre la ville de face en défonçant le pont-levis.");     } } class PasserParLaFaceNord : IStrategie {     public void MettreEnOeuvre() {         Console.WriteLine("Prendre la ville en escaladant la muraille nord.");     } } class AttendreQueLaVilleSeRende : IStrategie {     public void MettreEnOeuvre() {         Console.WriteLine("Attendre qu'il n'y ait plus rien à manger en ville "             + "et que tout le monde meure de faim.");     } } class SeMarierAvecLaCousineDuDuc : IStrategie {     public void MettreEnOeuvre() {         Console.WriteLine("Organiser un mariage avec la cousine du Duc "             + "alors qu'elle rejoint la ville de retour des Baléares "             + "et inviter toute la ville à une grande fête.");     } } /// <summary> Différentes situations </summary> enum Météo {     IlFaitBeau,     IlYADuBrouillard,     IlFaitTropChaudPourTravailler,     IlPleut }  class Program {     static void Main() {          // notre acteur         var kevin = new SeigneurDeLaGuerre();          // les aléas du système         var météo = (Météo)(new Random().Next(0, 4));          // une liaison tardive         switch (météo) {             case Météo.IlFaitBeau:                  kevin.Strategie = new DéfoncerLePontLevisDeFace(); break;             case Météo.IlYADuBrouillard:                  kevin.Strategie = new PasserParLaFaceNord(); break;             case Météo.IlFaitTropChaudPourTravailler:                 kevin.Strategie = new AttendreQueLaVilleSeRende(); break;             case Météo.IlPleut:                 kevin.Strategie = new SeMarierAvecLaCousineDuDuc(); break;             default:                  throw new Exception("Nan finalement seigneur de la guerre c'est "                     + "pas cool comme job : vous décidez d'aller cueillir "                     + "des champignons dans le Périgord.");         }          // une exécution aux petits oignons         kevin.PrendreLaVille();     } } 

Exemple en Delphi[modifier | modifier le code]

Source : Delphi GOF Design Patterns (CodePlex)

unit strategy;  interface  type   TContext = class;   IStrategy = interface  ['{7F63C143-98D0-4B8C-A02B-894D145BB745}']    function Move(c: TContext): integer;  end;   TStrategy1 = class(TInterfacedObject, IStrategy)  public    function Move(c: TContext): integer;  end;   TStrategy2 = class(TInterfacedObject, IStrategy)  public    function Move(c: TContext): integer;  end;   TContext = class  private    FStrategy: IStrategy;    FCounter: integer;  public    constructor Create(counter: integer);    function Algorithm: integer;    procedure SetStrategy(s: IStrategy);    property counter: integer read FCounter write FCounter;  end;  implementation  { TStrategy1 }  function TStrategy1.Move(c: TContext): integer; begin  c.Counter := c.Counter + 1;  Result := c.Counter; end;  { TStrategy2 }  function TStrategy2.Move(c: TContext): integer; begin  c.Counter := c.Counter - 1;  Result := c.Counter; end;  { TContext }  function TContext.Algorithm: integer; begin  Result := FStrategy.Move(Self) end;  constructor TContext.Create(counter: integer); begin  inherited;  FCounter := counter;  FStrategy := TStrategy1.Create; end;  procedure TContext.SetStrategy(s: IStrategy); begin  FStrategy := s; end;  end.   { fichier projet } program Behavioral.strategy.Pattern; {$APPTYPE CONSOLE}  uses   SysUtils,   strategy in 'strategy.pas';  var  context: TContext;  i: integer;  begin  try    context := TContext.Create(12);    context.SetStrategy(TStrategy1.Create);    try      for i := 0 to 30 do begin        if i =  15 then begin          WriteLn(#10 + '|| ');          context.SetStrategy(TStrategy2.Create);        end;        Write(IntToStr(context.Algorithm) + ' ');      end;      ReadLn;    finally      context.Free;    end;  except    on E:Exception do      Writeln(E.Classname, ': ', E.Message);  end; end. 

Exemple en Java[modifier | modifier le code]

Diagramme UML
Diagramme UML illustrant l'exemple

Nous savons que voler() et cancaner() sont les parties de la classe Canard qui varient d’un canard à l’autre.

Pour séparer ces comportements de la classe Canard, nous extrayons ces deux méthodes de la classe et nous créons un nouvel ensemble de classes pour représenter chaque comportement[2].


Canard utilise des attributs de type interface ComportementVol et ComportementCancan (le "bruit" du canard).

Ce sont ces interfaces qui encapsulent le code des méthodes effectuerVol() et effectuerCancan().

Ce sont ces méthodes que nous souhaitons encapsuler, selon le patron de conception stratégie, afin de les rendre interchangeables.

Malgré notre volonté de rendre ces méthodes interchangeables :

  • il reste d'autres méthodes que nous souhaitons conserver communes : ici la méthode nager().
  • et d'autres méthodes que nous souhaitons spécifique à l'implémentation choisie : ici la méthode afficher().
public abstract class Canard { 	ComportementVol comportementVol; 	ComportementCancan comportementCancan;  	public Canard() { 	}  	public abstract void afficher();  	public void effectuerVol() { 		comportementVol.voler(); 	}  	public void effectuerCancan() { 		comportementCancan.cancaner(); 	}  	public void nager() { 		System.out.println("Tous les canards flottent, même les leurres!"); 	}  	public void setComportementVol(ComportementVol comportementVol) { 		this.comportementVol = comportementVol; 	}  	public void setComportementCancan(ComportementCancan comportementCancan) { 		this.comportementCancan = comportementCancan; 	} } 

Maintenant, nous allons spécialiser cette classe Canard en implémentant deux nouvelles classes héritant de Canard. La classe Colvert :

public class Colvert extends Canard {     public Colvert() { 		comportementVol = new VolerAvecDesAiles(); 		comportementCancan = new Cancan(); 	}  	public void afficher() { 		System.out.println("Je suis un vrai colvert"); 	} } 

Et la classe PrototypeCanard :

public class PrototypeCanard extends Canard {     public PrototypeCanard() {         comportementVol = new NePasVoler();         comportementCancan = new Cancan();     }     public void afficher() {         System.out.println("Je suis un prototype de canard");     } } 

L'attribut comportementVol est un objet dont la classe implémente l'interface ComportementVol.

public interface ComportementVol { 	public void voler(); } 

L'attribut comportementCancan est un objet dont la classe implémente l'interface ComportementCancan.

public interface ComportementCancan {   public void cancaner() ; } 

Il est alors possible de spécifier autant de ComportementVol et de ComportementCancan que nécessaire, simplement en créant de nouvelle classe implémentant ces deux interfaces. À charge ensuite à ces classes d'implémenter les méthodes voler() ...

public class VolerAvecDesAiles implements ComportementVol { 	public void voler() { 		System.out.println("Je vole !!"); 	} }  public class NePasVoler implements ComportementVol { 	public void voler() { 		System.out.println("Je ne sais pas voler"); 	} } 

... et cancaner() pour spécifier le comportement désiré.

public class Cancan implements ComportementCancan { 	public void cancaner() { 		System.out.println("Cancan"); 	} } 

Exemple en Smalltalk[modifier | modifier le code]

Ici, le comportement d'une voiture peut être modifié en fonction de la stratégie de conduite:

ConduiteSport>>avance   Transcript show: 'à fond à fond'; cr  ConduiteTranquille>>avance   Transcript show: 'on roule doucement'; cr  Voiture>>modeSport   strategieConduite := ConduiteSport new  Voiture>>modeTranquille   strategieConduite := ConduiteTranquille new  Voiture>>avance   strategieConduite avance 

On peut donc écrire:

maVoiture := Voiture new.  maVoiture modeSport. maVoiture avance. "Affiche 'à fond à fond'"  maVoiture modeTranquille. maVoiture avance. "Affiche 'on roule doucement'" 

Notes et références[modifier | modifier le code]

  1. (en) Erich Gamma, Richard Helm, Ralph Johnson et John Vlissides, Design Patterns: Elements of Reusable Software, Addison-Wesley, (ISBN 0-201-63361-2), p. 316
  2. Freeman, Eric, 1965-, Freeman, Elisabeth., Sierra, Kathy. et Bates, Bert. (trad. de l'anglais), Design patterns, tête la première, Beijing/Cambridge/Paris etc., O'Reilly, , 637 p. (ISBN 2-84177-350-7 et 978-2-84177-350-3, OCLC 181354957, lire en ligne)