Top Code 2024, les challenges sont de nouveau disponibles dans les boards pour les participant(e)s => Boards
T: / Corrigés des challenges / PHP
Présentation et implémentation du design pattern Strategy en PHP.
Dans le challenge RUGBY_1, l’objectif est de calculer l’impact total de la mêlée, en appliquant une méthode de calcul différente pour chaque ligne. Il y a en effet 3 lignes de joueurs avec chacune un comportement différent. Pour autant chaque ligne a une fonctionnement similaire, mais un nombre de joueurs différents, et un calcul de l’impact différent.
L’idée ici est donc de structurer ces différentes méthodes de calcul avec le design pattern Strategy.
Au programme de ce corrigé :
Le design pattern Strategy permet de définir une famille d’algorithmes, de les encapsuler chacun dans une classe séparée, et de les rendre interchangeables. Cela signifie que le choix de l’algorithme peut varier dynamiquement selon le contexte d’exécution, sans que l’utilisateur du code n’ait besoin de connaître les détails de l’implémentation.
Chaque Ligne de la Mêlée aura donc sa propre stratégie de calcul d’impact.
On n’est pas ici sur un diagramme UML au sens strict mais il va permettre de visualiser comment les classes sont organisées :
Quelques explications sur ce schéma et quelques rappels sur la programmation orientée objet au passage :
On commence donc par l’interface. Une interface ne contient que les déclarations des méthodes qui seront à implémenter.
interface ImpactStrategy
{
public function calculateImpact(Player $player): int;
}
Et chaque stratégie, une par ligne. Chaque stratégie doit implémenter la méthode calculateImpact, avec chacune sa version :
Première ligne :
class FirstLineStrategy implements ImpactStrategy
{
public function calculateImpact(Player $player): int
{
return (int) floor($player->impact() * 1.5);
}
}
Seconde ligne :
class SecondLineStrategy implements ImpactStrategy
{
public function calculateImpact(Player $player): int
{
return $player->impact();
}
}
Troisième ligne :
class ThirdLineStrategy implements ImpactStrategy
{
public function calculateImpact(Player $player): int
{
return (int) floor($player->impact() * 0.75);
}
}
Chaque ligne applique donc son coefficient. La seconde ligne n’a pas de coefficient, on retourne donc directement l’impact du joueur.
Ici le contenu de chaque version de la méthode calculateImpact est simple. Mais on pourrait bien entendu avoir des choses beaucoup plus complexes et surtout beaucoup plus éloignées d’un point de vue fonctionnel pour les différentes stratégies.
Commençons pas la classe Player :
class Player
{
public function __construct(
public readonly int $poids,
public readonly int $force
) {}
public static function createFromText(string $informations): Player
{
$values = explode(':', $informations);
return new self(
(int) $values[0],
(int) $values[1]
);
}
public function impact(): int
{
return $this->poids * $this->force;
}
}
Un peu d’explications :
Voici la classe Line, c’est à ce niveau qu’est utilisée le pattern Strategy :
class Line
{
/**
* @var Player[] $players
*/
private array $players = [];
private ImpactStrategy $strategy;
/**
* @param string[] $playersInformations
*/
public function __construct(array $playersInformations, ImpactStrategy $strategy)
{
foreach ($playersInformations as $playerInformations) {
$this->players[] = Player::createFromText($playerInformations);
}
$this->strategy = $strategy;
}
public function impact(): int
{
$impact = 0;
foreach ($this->players as $player) {
$impact += $this->strategy->calculateImpact($player);
}
return $impact;
}
}
Un peu d’explications :
Puis la mêlée, la classe Scrum :
class Scrum
{
public function __construct(
private Line $firstLine,
private Line $secondLine,
private Line $thirdLine,
) {}
public function impact(): int
{
return $this->firstLine->impact() + $this->secondLine->impact() + $this->thirdLine->impact();
}
}
Un peu d’explications :
Et enfin, le programme principal :
// $line1, $line2 et $line3 sont les données du challenge
$scrum = new Scrum(
new Line($line1, new FirstLineStrategy),
new Line($line2, new SecondLineStrategy),
new Line($line3, new ThirdLineStrategy),
);
// Impact total
$scrum->impact();
Le code complet est disponible sur Github.
On peut se demander pourquoi on passe des « new FirstLineStrategy » en argument du constructeur, et pourquoi ne fait pas par exemple :
new Line($line1, 'FirstLineStrategy');
// Pour faire ensuite dans le constructeur de Line
$strategy = new $strategyName;
En passant directement l’instance en paramètres, on bénéficie de ces 2 avantages :
Pour conclure, on a vu comment le design pattern Strategy permet de créer une famille d’algorithme qui ont un comportement similaire, de les organiser correctement dans différentes classes, et de les utiliser avec d’autres classes qui en ont besoin. Quelques cas pratiques pour lesquels il peut être judicieux d’utiliser le design pattern Strategy (à adapter bien sûr selon le contexte de TON code et de TON besoin) :
Pour t’entrainer sur le design pattern Strategy en PHP, tu peux par exemple l’appliquer sur le challenge Pôle Express, en définissant des stratégies pour chaque type d’élément d’une commande.
Le code a été entièrement testé avec Pest PHP. Les tests sont disponibles sur Github également.
Autres contenus à découvrir
Corrigés, challenges, actualités, veille technique... aucun spam.