T: / Corrigés des challenges / PHP
Présentation et implémentation du design pattern Factory en PHP.
Dans le challenge GOT_1, l’objectif est de déterminer la composition de l’armée de Daenerys en fonction du nombre d’ennemis à affronter. Pour cela, elle a 3 types de troupes à disposition : des Dragons, des Immaculés, et des Dothrakis (tu ne connais pas ces termes, t’inquiète pas c’est pas grave, promis, on en parle plus après 😉 ) Pour chaque troupe, les mécaniques sont semblables.
L’idée ici va donc être de structurer correctement les troupes avec un ensemble de classes pour pouvoir les générer à la volée et appliquer ces mécaniques. Pour cela on va utiliser le design pattern Factory.
Au programme de ce corrigé :
Le design pattern Factory permet de créer des objets sans spécifier la classe à instancier. C’est à dire qu’on ne fait pas un « new MaClasse ». Au lieu de ça, on passe par une méthode de fabrication qui prend en charge l’instanciation de la classe et son renvoi. Cette méthode prendra généralement des paramètres en entrée pour déterminer quelle classe instancier. L’exemple le plus basique : « Factory::build(‘MaClasse’) ».
Ce pattern permet notamment de découper le code en séparant la logique de création d’objets du programme principal, rendant le système plus modulaire, plus facile à étendre et donc à maintenir. Ce challenge GOT_1 est le parfait candidat pour illustrer tout ça !
On n’est pas ici sur un diagramme UML au sens strict mais il va permettre de visualiser comment les fichiers sont organisés :
Quelques explications sur ce schéma et quelques rappels sur la programmation orientée objet au passage :
Voici le contenu de la classe Troops :
namespace Challenges\GOT_1;
abstract class Troops
{
protected int $nbTroops;
public function __construct(
protected int $army
) {}
abstract public function calculNumberOfTroops(): void;
abstract public function getRealNumberOfTroops(): int;
public function getNbTroops(): int
{
return $this->nbTroops;
}
}
Un peu d’explications :
Voici le contenu de la classe Dragons (les 2 autres seront très similaires) :
namespace Challenges\GOT_1\Troops;
use Challenges\GOT_1\Troops;
class Dragons extends Troops
{
public function calculNumberOfTroops(): void
{
$troops = floor($this->army / 3);
$troops = floor($troops / 1000);
$this->nbTroops = (int) min([$troops, 3]);
}
public function getRealNumberOfTroops(): int
{
return $this->nbTroops * 1000;
}
}
Un peu d’explications :
Si on reprend le schéma UML, on a décidé de créer une méthode statique qui prend 2 paramètres :
Voici le code :
class TroopsFactory
{
public static function recruit(string $className, int $army): Troops
{
if (! class_exists('Challenges\\GOT_1\\Troops\\' . $className)) {
throw new \Exception('La classe ' . $className . ' n\'existe pas');
}
$class = 'Challenges\\GOT_1\\Troops\\' . $className;
$instance = new $class($army);
// Pour PHPStan
assert($instance instanceof Troops);
return $instance;
}
}
Un peu d’explications :
Cette méthode peut donc être utilisée pour instancier indifféremment les classes « Dragons », « Immacules » ou « Dothrakis ».
Voici le contenu de la classe Commandant :
class Commandant
{
/**
* @var int[] $composition
*/
private array $composition = [];
public function __construct(
private int $army
) {}
/**
* @param string[] $troops
*/
public function determineTroops(array $troops): void
{
foreach ($troops as $troopClass) {
$troop = TroopsFactory::recruit($troopClass, $this->army);
$troop->calculNumberOfTroops();
$this->composition[] = $troop->getNbTroops();
$this->army -= $troop->getRealNumberOfTroops();
}
}
public function getComposition(): string
{
return implode('_', $this->composition);
}
}
Un peu d’explications :
Et enfin, l’utilisation de cette classe Commandant :
$daenerys = new Commandant($armee);
// La liste des troupes à "recruter" pour la bataille !
$daenerys->determineTroops(['Dragons', 'Immacules', 'Dothrakis']);
echo $daenerys->getComposition();
Ce qui est intéressant de noter ici c’est qu’il sera très facile de créer un nouveau type de troupe. Pour cela il faudrait :
On a ici une bonne illustration du découpage de code entre les objets spécifiques, la création de ces objets et notre programme principal.
Le design pattern Factory sera particulièrement utile dans ce genre de situation, où un objet doit être créé en fonction de configurations dynamiques, d’options ou de types qui ne sont déterminés qu’au moment de l’exécution.
Des challenges qui peuvent te permettre de mettre en œuvre ce pattern :
Le code complet de ce corrigé (avec notamment les 2 autres classes) peut être retrouvé sur Github.
Le code a été entièrement testé avec Pest PHP. Les tests sont disponibles sur Github également.
Ces tests permettent notamment de mettre en avant le concept de dataset, qui permet de passer plusieurs jeux de données à un même test. Je teste ainsi de façon très efficace les différents cas possibles et limites pour la composition de l’armée. Et je m’appuie aussi sur les jeux de données proposés par Tainix pour construire les tests !
Résultat en image :
Other content to discover
Corrections, challenges, news, technical monitoring... no spam.