Accueil /  Blog / Symfony / Le pattern Stratégie avec Symfony

Le pattern Stratégie avec Symfony

Publié le jeudi 9 février 2023

C'est un modèle de conception de logiciel qui permet de séparer l'algorithme d'une classe de son exécution. Il est utilisé pour résoudre les problèmes de complexité et de maintenance dans les applications.

Le pattern Stratégie

En utilisant ce pattern, nous pouvons facilement ajouter ou remplacer des algorithmes sans altérer les autres parties de l'application. Cela permet une plus grande flexibilité et une meilleure réutilisation du code.

Exemple concret

Imaginons un système simple mais efficace pour l'utilisation de ce pattern, par exemple la mise en place de différents types de réduction sur des commandes.

Nous devons pouvoir affecter une remise fixe et / ou une remise en %.

Mise en oeuvre avec Symfony

Premièrement, créez une interface qui définit les algorithmes nécessaires pour une tâche donnée. Ici nous voulons calculer la remise sur des commandes. Nous aurons donc l'interface DiscountStrategy avec une méthode calculateDiscount.

namespace App\Strategy;

interface DiscountStrategyInterface
{
    public function calculateDiscount(int $totalAmount): float;
}

Deuxièmement, créez des classes concrètes qui implémentent l'interface DiscountStrategy. Par exemple, nous pouvons avoir une classe FixedDiscountStrategy qui applique une remise fixe sur les commandes et une classe PercentageDiscountStrategy qui applique une remise en pourcentage.

namespace App\Strategy;

class FixedDiscountStrategy implements DiscountStrategyInterface
{
    private int $discountAmount;

    public function __construct(int $discountAmount)
    {
        $this->discountAmount = $discountAmount;
    }

    public function calculateDiscount(int $totalAmount): float
    {
    // On applique une réduction fix on retourne donc uniquement le montant de celle ci
        return $this->discountAmount;
    }
}
namespace App\Strategy;

class PercentageDiscountStrategy implements DiscountStrategyInterface
{
    private $discountPercentage;

    public function __construct(int $discountPercentage)
    {
        $this->discountPercentage = $discountPercentage;
    }

    public function calculateDiscount(int $totalAmount): float
    {
        // On applique une réduction par % et on retourne le montant total de la réduction
        return (int) ($totalAmount * ($this->discountPercentage / 100));
    }
}

Troisièmement, une classe Order tout ce qu'il y a de plus standard.

namespace App\Entity;

use App\Strategy\DiscountStrategy;

class Order
{

    public function __construct(
        private ?float $totalAmount = null
    ){
    }
    //Logique Order

}

Quatrièmement, il nous faudra determiner quelle strategie utiliser. Nous avons décidé de représenter cela dans un service, qui pourra être appelé depuis un controller ou ailleurs. Mais libre à vous de faire autrement !

namespace App\Service;

use App\Entity\Order;
use App\Strategy\FixedDiscountStrategy;
use App\Strategy\PercentageDiscountStrategy;

class OrderDiscountCalculator {

    public function calculateDiscount(Order $order): float
    {
        $strategy = match (true) {
            (new DateTime())->format('m') === '07') => new FixedDiscountStrategy(),
            $order->getTotalAmount() > 100 => new PercentageDiscountStrategy(),
            // Our new Strategies
        }

        return $strategy->getDiscountedAmount($order);
    }
}



Dans ce service, on peut voir que nous avons défini des stratégies à utiliser en fonction de divers paramètres définis totalement arbitrairement, afin de récupérer le montant total de la réduction à appliquer.

Allons plus loin et imaginons un exemple d'utilisation dans un controller qui nous renverrait un json avec le montant de la réduction voici ce qu'on aurait :

namespace App\Controller:

use App\Entity\Order;
use Symfony\Component\HttpFoundation\JsonResponse;
use App\Service\OrderDiscountCalculator

public function getCurrentDiscountAmount(Order $sorder, OrderDiscountCalculator $calculator): JsonResponse
{
    return new JsonResponse([
        'discountAmout' => $calculator->calculateDiscount($sorder)
    ]);
}

Bien évidemment, ne pas oublier un peu de configuration afin de renseigner les informations nécessaires au bon fonctionnement.

services:
    App\Strategy\FixedDiscountStrategy:
        arguments:
            $discountAmount: '%fixed_discount_amount%'

    App\Strategy\PercentageDiscountStrategy:
        arguments:
            $discountPercentage: '%percentage_discount_percentage%'

Conclusion

Il est important de noter que le Design Pattern Strategy n'est pas le seul modèle de conception disponible pour résoudre les problèmes de complexité et de maintenance dans les applications. Il existe par exemple la chaîne de responsabilité qui vous est également présentée sur notre blog. Cependant, il est souvent considéré comme une solution efficace pour améliorer la flexibilité et la réutilisabilité de votre code.

Cependant, il ne doit pas être utilisé à tort et à travers au risque de complexifier votre code pour rien. Par exemple ce pattern est souvent utilisé dans un contexte variable. On peut ainsi le coupler avec le pattern Factory afin d'éviter une succession de if et simplifier un code complexe.

Pour résumer plus simplement en citant Guru.

Si vous n’avez que quelques algorithmes qui ne varient pas beaucoup, nul besoin de rendre votre programme plus compliqué avec les nouvelles classes et interfaces qui accompagnent la mise en place du patron.

En conclusion, le Design Pattern Strategy est un modèle de conception utile pour améliorer la qualité et la flexibilité de votre code. Il permet de séparer les algorithmes de leur exécution, ce qui facilite l'ajout ou le remplacement des algorithmes sans affecter les autres parties de l'application.

Suivez notre actualité en avant première. Pas plus d’une newsletter par mois.