State Pattern
- Type : Comportemental
- Difficulté : 6/10
- Définition succincte : Le State Pattern est un design pattern comportemental qui permet à un objet de changer de comportement en fonction de son état interne, en déléguant les responsabilités de chaque état à des objets spécifiques. Ce pattern permet d'éviter de nombreuses conditions
ifouswitchdans le code et rend l'objet plus modulaire et extensible.
Objectif du State Pattern
L'objectif du State Pattern est de permettre à un objet de modifier son comportement lorsque son état change. Ce pattern est utile lorsque l'objet doit adopter différents comportements en fonction de son état actuel, sans avoir à utiliser de conditions complexes dans ses méthodes. Chaque état est représenté par une classe séparée, ce qui rend le code plus modulaire et facile à maintenir.
Structure du State Pattern
- State : Interface ou classe abstraite qui définit les comportements spécifiques à chaque état.
- ConcreteState : Implémentations concrètes des différents états de l'objet.
- Context : L'objet principal dont le comportement varie en fonction de son état. Il délègue les comportements spécifiques à son état actuel.
Implémentation avec Laravel
Prenons un exemple dans une application Laravel où tu gères un système de commandes pour une boutique en ligne. Chaque commande peut passer par plusieurs états : nouvelle, en traitement, expédiée, et livrée. Le State Pattern permet de gérer le comportement de la commande de manière propre et modulable en fonction de son état.
a) Créer l'interface OrderState (State)
L'interface OrderState définit les méthodes que chaque état de la commande doit implémenter. Par exemple, la méthode proceedToNext() sera différente selon l'état actuel de la commande.
// app/Contracts/OrderState.php
namespace App\Contracts;
interface OrderState
{
public function proceedToNext(): void;
public function getStatus(): string;
}
b) Créer la classe Order (Context)
La classe Order est le contexte qui change de comportement en fonction de son état interne. Elle contient une référence à l'objet OrderState qui représente l'état actuel.
// app/Models/Order.php
namespace App\Models;
use App\Contracts\OrderState;
class Order
{
private OrderState $state;
public function __construct(OrderState $state)
{
$this->setState($state);
}
public function setState(OrderState $state): void
{
$this->state = $state;
}
public function proceedToNext(): void
{
$this->state->proceedToNext();
}
public function getStatus(): string
{
return $this->state->getStatus();
}
}
c) Créer les états concrets (ConcreteState)
Chaque état de la commande va implémenter l'interface OrderState avec des comportements spécifiques. Voici les quatre états de notre système de gestion de commandes.
1. État "Nouvelle commande" (NewOrderState)
// app/States/NewOrderState.php
namespace App\States;
use App\Contracts\OrderState;
use App\Models\Order;
class NewOrderState implements OrderState
{
private Order $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function proceedToNext(): void
{
$this->order->setState(new ProcessingOrderState($this->order));
echo "Commande passée à l'état 'En traitement'.\n";
}
public function getStatus(): string
{
return 'Nouvelle commande';
}
}
2. État "En traitement" (ProcessingOrderState)
// app/States/ProcessingOrderState.php
namespace App\States;
use App\Contracts\OrderState;
use App\Models\Order;
class ProcessingOrderState implements OrderState
{
private Order $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function proceedToNext(): void
{
$this->order->setState(new ShippedOrderState($this->order));
echo "Commande passée à l'état 'Expédiée'.\n";
}
public function getStatus(): string
{
return 'En traitement';
}
}
3. État "Expédiée" (ShippedOrderState)
// app/States/ShippedOrderState.php
namespace App\States;
use App\Contracts\OrderState;
use App\Models\Order;
class ShippedOrderState implements OrderState
{
private Order $order;
public function __construct(Order $order)
{
$this->order = $order;
}
public function proceedToNext(): void
{
$this->order->setState(new DeliveredOrderState($this->order));
echo "Commande passée à l'état 'Livrée'.\n";
}
public function getStatus(): string
{
return 'Expédiée';
}
}
4. État "Livrée" (DeliveredOrderState)
// app/States/DeliveredOrderState.php
namespace App\States;
use App\Contracts\OrderState;
use App\Models\Order;
class DeliveredOrderState implements OrderState
{
public function proceedToNext(): void
{
echo "La commande est déjà livrée.\n";
}
public function getStatus(): string
{
return 'Livrée';
}
}
d) Utilisation du State Pattern dans une commande CLI
Nous allons maintenant créer une commande CLI pour simuler le changement d'état d'une commande.
// app/Console/Commands/ProcessOrder.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Order;
use App\States\NewOrderState;
class ProcessOrder extends Command
{
protected $signature = 'order:process';
protected $description = 'Simule le processus de changement d\'état d\'une commande';
public function handle()
{
// Créer une nouvelle commande avec l'état "Nouvelle commande"
$order = new Order(new NewOrderState($order));
// Simuler le passage d'un état à l'autre
$this->info('État actuel: ' . $order->getStatus());
$order->proceedToNext();
$this->info('État actuel: ' . $order->getStatus());
$order->proceedToNext();
$this->info('État actuel: ' . $order->getStatus());
$order->proceedToNext();
$this->info('État actuel: ' . $order->getStatus());
// Essayer de passer à l'état suivant une fois que la commande est livrée
$order->proceedToNext();
}
}
e) Exécution de la commande
Tu peux exécuter la commande pour voir comment la commande passe d'un état à l'autre.
php artisan order:process
Résultat attendu :
État actuel: Nouvelle commande
Commande passée à l'état 'En traitement'.
État actuel: En traitement
Commande passée à l'état 'Expédiée'.
État actuel: Expédiée
Commande passée à l'état 'Livrée'.
État actuel: Livrée
La commande est déjà livrée.
Avantages du State Pattern
- Élimine les conditions complexes : Le State Pattern permet d'éviter l'utilisation de nombreux
ifouswitchpour gérer les changements d'état. - Responsabilités bien séparées : Chaque état est encapsulé dans une classe séparée, ce qui rend le code plus modulaire et maintenable.
- Facilité d'ajout d'états : Ajouter de nouveaux états ou modifier des états existants devient plus simple, car il suffit de créer ou de modifier des classes d'états spécifiques.
Inconvénients du State Pattern
- Multiplication des classes : Chaque état doit avoir sa propre classe, ce qui peut conduire à une multiplication des classes dans les systèmes avec de nombreux états.
- Complexité initiale : Le State Pattern peut sembler plus complexe à mettre en place au départ que de simples conditions
if, surtout pour des cas simples.
Conclusion
Le State Pattern est un excellent choix pour gérer des objets dont le comportement varie en fonction de leur état. En Laravel, ce pattern peut être utilisé pour gérer des flux de travail ou des étapes de processus complexes, comme la gestion des commandes dans une boutique en ligne. Il permet de rendre le code plus modulaire et maintenable en séparant les responsabilités de chaque état dans des classes dédiées.