Factory Method Pattern
- Type : Créationnel
- Difficulté : 4/10
- Définition succincte : Le Factory Method Pattern est un design pattern de création qui définit une interface pour créer des objets, mais laisse aux sous-classes le soin de déterminer quelle classe concrète instancier. Cela permet de déléguer la responsabilité de la création d'objets à des sous-classes, tout en laissant la classe parente abstraite sur les détails de l'instanciation.
- Contexte spécifique à Laravel : En Laravel, le Factory Method Pattern est couramment utilisé dans les Database Factories et les Test Factories, où l'on délègue la création d'instances de modèles à une méthode spécialisée qui décide comment et quoi instancier.
Objectif du Factory Method Pattern
L'objectif principal du Factory Method Pattern est de centraliser et standardiser la création d'objets dans un système, tout en permettant aux sous-classes de personnaliser cette création. Cela est utile lorsque le code client n'a pas besoin de connaître la classe exacte qui sera instanciée, ou lorsque le type d'objet à instancier dépend de certaines conditions.
Structure du Factory Method Pattern
- Product : Interface ou classe abstraite qui définit le type d'objet à créer.
- ConcreteProduct : Classe concrète qui implémente ou hérite de
Product, c'est l'objet créé par leFactory Method. - Creator : Classe abstraite ou interface qui déclare le
Factory Methodpour créer des objets de typeProduct. LeFactory Methodpeut être abstrait ou avoir une implémentation par défaut. - ConcreteCreator : Classe concrète qui hérite de
Creatoret implémente leFactory Methodpour créer des instances deConcreteProduct.
1. Exemple d'utilisation dans Laravel
Dans Laravel, les Database Factories utilisent le Factory Method Pattern pour générer des instances de modèles. Voici un exemple où une factory est utilisée pour créer un utilisateur :
$user = \App\Models\User::factory()->create();
Laravel utilise une méthode factory() pour déléguer l'instanciation du modèle à une classe spécifique sans que tu aies besoin de t'occuper des détails. Cette logique est un exemple d'implémentation du Factory Method Pattern.
2. Implémentation complète dans Laravel
Imaginons que tu développes un système qui génère différents types de documents (comme des PDF, des rapports en CSV, et des invitations). Chaque document a un processus de création légèrement différent, mais le système doit les traiter de manière uniforme.
Le Factory Method Pattern permet de créer une interface ou une classe abstraite pour créer des documents, puis de laisser les sous-classes décider du type de document à générer.
a) Définir l'interface Document
Tu commences par créer une interface ou une classe abstraite pour les documents. Cela définit le contrat pour la génération de documents.
// app/Contracts/Document.php
namespace App\Contracts;
interface Document
{
public function generate();
}
b) Créer les classes concrètes de document
Ensuite, tu implémentes différentes classes de documents qui respectent ce contrat.
2.1. Classe PdfDocument
// app/Documents/PdfDocument.php
namespace App\Documents;
use App\Contracts\Document;
class PdfDocument implements Document
{
public function generate()
{
return "PDF document generated!";
}
}
2.2. Classe CsvReport
// app/Documents/CsvReport.php
namespace App\Documents;
use App\Contracts\Document;
class CsvReport implements Document
{
public function generate()
{
return "CSV report generated!";
}
}
2.3. Classe InvitationDocument
// app/Documents/InvitationDocument.php
namespace App\Documents;
use App\Contracts\Document;
class InvitationDocument implements Document
{
public function generate()
{
return "Invitation document generated!";
}
}
c) Créer le Factory Method
Tu crées ensuite une classe abstraite DocumentFactory qui déclare le Factory Method. Ce Factory Method sera responsable de la création de documents, mais sans définir explicitement quelle sous-classe instancier.
// app/Factories/DocumentFactory.php
namespace App\Factories;
abstract class DocumentFactory
{
abstract public function createDocument(): Document;
public function generateDocument()
{
$document = $this->createDocument();
return $document->generate();
}
}
d) Implémenter les Concrete Creators
Maintenant, tu vas créer des sous-classes concrètes qui implémentent le Factory Method pour générer les différents types de documents.
4.1. Classe PdfDocumentFactory
// app/Factories/PdfDocumentFactory.php
namespace App\Factories;
use App\Documents\PdfDocument;
use App\Contracts\Document;
class PdfDocumentFactory extends DocumentFactory
{
public function createDocument(): Document
{
return new PdfDocument();
}
}
4.2. Classe CsvReportFactory
// app/Factories/CsvReportFactory.php
namespace App\Factories;
use App\Documents\CsvReport;
use App\Contracts\Document;
class CsvReportFactory extends DocumentFactory
{
public function createDocument(): Document
{
return new CsvReport();
}
}
4.3. Classe InvitationDocumentFactory
// app/Factories/InvitationDocumentFactory.php
namespace App\Factories;
use App\Documents\InvitationDocument;
use App\Contracts\Document;
class InvitationDocumentFactory extends DocumentFactory
{
public function createDocument(): Document
{
return new InvitationDocument();
}
}
e) Utiliser le Factory Method dans une commande CLI
Tu peux maintenant utiliser le Factory Method Pattern dans une commande CLI pour générer différents types de documents sans te soucier des détails d'instanciation.
// app/Console/Commands/GenerateDocument.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Factories\PdfDocumentFactory;
use App\Factories\CsvReportFactory;
use App\Factories\InvitationDocumentFactory;
class GenerateDocument extends Command
{
protected $signature = 'document:generate {type}';
protected $description = 'Generate a specific type of document';
public function handle()
{
$type = $this->argument('type');
$factory = $this->getFactory($type);
if ($factory) {
$result = $factory->generateDocument();
$this->info($result);
} else {
$this->error("Unknown document type: $type");
}
}
protected function getFactory($type)
{
return match($type) {
'pdf' => new PdfDocumentFactory(),
'csv' => new CsvReportFactory(),
'invitation' => new InvitationDocumentFactory(),
default => null,
};
}
}
f) Exécuter la commande
Tu peux maintenant utiliser la commande pour générer les documents. Par exemple :
php artisan document:generate pdf
Renvoie :
PDF document generated!
Ou :
php artisan document:generate csv
Renvoie :
CSV report generated!
Exemple de résultat
En exécutant la commande avec différents arguments (pdf, csv, invitation), le Factory Method est utilisé pour créer et générer le bon type de document :
- document:generate pdf : Renvoie
"PDF document generated!" - document:generate csv : Renvoie
"CSV report generated!" - document:generate invitation : Renvoie
"Invitation document generated!"
Avantages du Factory Method Pattern
- Centralisation de la création : La création d'objets est centralisée dans des factory methods, ce qui permet de modifier la logique de création d'un objet sans toucher au code client.
- Extensibilité : Ajouter de nouveaux types d'objets devient facile. Il suffit de créer une nouvelle sous-classe de
DocumentFactoryet de l'utiliser sans modifier le code client. - Découplage : Le client est découplé des détails spécifiques d'instanciation des objets concrets. Le client travaille avec des interfaces ou des classes abstraites.
Inconvénients du Factory Method Pattern
- Complexité supplémentaire : Ce pattern peut introduire une complexité supplémentaire si la création d'objets est simple ou n'a pas besoin d'être personnalisée.
- Surcharge de sous-classes : S'il y a beaucoup de types d'objets à créer, cela peut entraîner une explosion de sous-classes, augmentant la complexité du projet.
Conclusion
Le Factory Method Pattern est un excellent moyen de centraliser la création d'objets et de permettre aux sous-classes de personnaliser cette création. En PHP et Laravel, ce pattern est utile pour des scénarios où la création d'objets est complexe ou doit être ajustée en fonction des différents types d'objets. Il permet également de découpler le code client de l'instanciation, rendant le système plus
flexible et extensible.