Accueil / Tutoriels / Strategy

Strategy

Publié le 24 Mars 2025
Conception

Strategy (stratégie en français) est un design pattern comportemental qui permet de définir une famille d’algorithmes, de les encapsuler dans des classes séparées et de les rendre interchangeables.

Principe

Dans le cas où dans notre code il existe plusieurs algorithmes (qu’on peut nommer stratégies) similaires, au lieu d’utiliser des structures conditionnelles (if ou switch) pour choisir quelle stratégie est exécutée, ce qui alourdit le code, on crée des classes distinctes qui vont partager une interface commune. Ainsi, il est beaucoup plus facile d’ajouter du nouveau code sans toucher aux classes existantes.

Ce design pattern favorise la composition plutôt que l’héritage. On peut changer de stratégie à la volée tout en gardant un code plus lisible et maintenable, chaque algorithme étant dans une classe distincte.

Exemples d’utilisations

Système de login avec plusieurs méthodes de connexion

Sur un site web, les utilisateurs peuvent se connecter par différents moyens (email, Google OAuth, Apple OAuth, code par SMS, etc…). En utilisant ici le pattern Strategy, on évite un gros bloc de conditions dans la logique d’authentification. Si l’on souhaite ajouter un nouveau mode de connexion, cela peut se faire très facilement sans toucher au code existant.

Gestion des frais de livraison sur un site de e-commerce

En fonction du transporteur et de la destination, un site de e-commerce doit calculer des frais de livraison. Ici chaque transporteur va avoir une logique de calcul différente qui peut évoluer indépendamment des autres. Le jour où le site propose un nouveau transporteur, il est très facile d’ajouter dans le code une logique spécifique à ce transporteur.

Points d’attentions

Chaque stratégie correspondant à une classe distincte, le code peut devenir complexe si le nombre de stratégies est important. La multiplication des classes peut rendre le suivi du flux d’exécution plus complexe et rendre le débogage plus difficile.

Le choix de la bonne “stratégie” est géré ailleurs dans le code, ce qui rajoute une complexité supplémentaire. On peut être tenté de gérer ce choix par une structure de type if ou switch, précisément ce que l’on cherche à éviter en utilisant ce pattern. Dans ce cas, le pattern Factory peut être utile pour prévenir l’utilisation de structures conditionnelles tout en centralisant la création d’objets.

Attention à ne pas tomber dans une “sur-ingénierie”. Si le nombre de stratégies est faible et/ou amenés à ne pas changer souvent, l'utilisation de ce pattern n’est pas très pertinente.

Implémenter le pattern Strategy

On va définir une classe qui sert de “contexte” et qui contient une référence vers une stratégie et l’utilise pour exécuter son comportement. Ici le contexte fait office d’interface générique qui permet d’utiliser une stratégie.

Une interface commune à toutes les classes des stratégies est définie avec la signature de la méthode à exécuter.

Pour chaque algorithme de notre code, on crée une classe concrète qui implémente la stratégie spécifique.

Exemple d'utilisation de Strategy en Go :


package shippingStrategy

// Définition de l'interface Strategy
type ShippingStrategy interface {
   CalculateCost(weight float64, distance float64) float64
}


package shippingStrategy

type ShippingContext struct {
   strategy ShippingStrategy
}

func (s *ShippingContext) SetStrategy(strategy ShippingStrategy) {
   s.strategy = strategy
}

func (s *ShippingContext) CalculateShipping(weight float64, distance float64) float64 {
   return s.strategy.CalculateCost(weight, distance)
}



package shippingStrategy

type Colissimo struct{}

func (c Colissimo) CalculateCost(weight float64, distance float64) float64 {
   return 5.0
}


package shippingStrategy

type Chronopost struct{}

func (cr Chronopost) CalculateCost(weight float64, distance float64) float64 {
   return weight*0.5 + distance*0.2
}


package shippingStrategy

type MondialRelay struct{}

func (mr MondialRelay) CalculateCost(weight float64, distance float64) float64 {
   if distance < 100 {
      return 4.0
   }

   return 7.0
}


package main

import (
   "fmt"
   "designPatterns/strategy/shippingStrategy"
)

func main() {
   context := shippingStrategy.ShippingContext{}

   context.SetStrategy(shippingStrategy.Colissimo{})
   fmt.Println("Colissimo : ", context.CalculateShipping(2, 50))

   context.SetStrategy(shippingStrategy.Chronopost{})
   fmt.Println("Chronopost : ", context.CalculateShipping(2, 50))

   context.SetStrategy(shippingStrategy.MondialRelay{})
   fmt.Println("Mondial Relay : ", context.CalculateShipping(2, 150))
}

Le même exemple en PHP :


namespace Practice\DesignPatterns\Strategy;

interface ShippingStrategyInterface
{
   public function calculateCost(float $weight, float $distance): float;
}


namespace Practice\DesignPatterns\Strategy;

class ShippingContext
{
   private ShippingStrategyInterface $strategy;

   public function setStrategy(ShippingStrategyInterface $strategy): void
   {
       $this->strategy = $strategy;
   }

   public function calculateShipping(float $weight, float $distance): float
   {
       return $this->strategy->calculateCost($weight, $distance);
   }
}


namespace Practice\DesignPatterns\Strategy;

class Colissimo implements ShippingStrategyInterface
{
   public function calculateCost(float $weight, float $distance): float
   {
       return 5.0;
   }
}

namespace Practice\DesignPatterns\Strategy;

class Chronopost implements ShippingStrategyInterface
{
   public function calculateCost(float $weight, float $distance): float
   {
       return $weight * 0.5 + $distance * 0.2;
   }
}


namespace Practice\DesignPatterns\Strategy;

class MondialRelay implements ShippingStrategyInterface
{
   public function calculateCost(float $weight, float $distance): float
   {
       return $distance < 100 ? 4.0 : 7.0;
   }
}


declare(strict_types=1);

use Practice\DesignPatterns\Strategy\Chronopost;
use Practice\DesignPatterns\Strategy\Colissimo;
use Practice\DesignPatterns\StrategyMondialRelay;
use Practice\DesignPatterns\Strategy\ShippingContext;

require "./vendor/autoload.php";

$context = new ShippingContext();

$context->setStrategy(new Colissimo());
echo "Colissimo : " . $context->calculateShipping(2, 50);

$context->setStrategy(new Chronopost());
echo "Chronopost : " . $context->calculateShipping(2, 50);

$context->setStrategy(new MondialRelay());
echo "Mondial Relay : " . $context->calculateShipping(2, 150);

Conclusion

J'espère que grâce à cet article vous êtes désormais plus à l'aise avec le design pattern Strategy. Vous pouvez retrouver mon article sur les patrons de conception ou les autres tutoriels sur Iterator, Prototype, Adapter et Factory.