← Tilbage til patterns

Factory Pattern

Definerer en interface til at oprette objekter, men lader subklasser bestemme hvilken klasse der skal instantieres.

CreationalBegynder-venlig

Om Patterned

Factory Pattern er et fundamentalt kreativt design pattern introduceret af Gang of Four i 1994, der løser problemet med at oprette objekter uden at specificere deres eksakte klasser. Patterned fungerer ved at definere en separat metode eller klasse til at håndtere objekt creation logic, hvilket giver fleksibilitet til at ændre hvilke objekter der oprettes uden at ændre koden der bruger dem. Dette er særligt værdifuldt når objektoprettelse involverer kompleks logik, afhænger af runtime betingelser, eller når du vil afkoble klienten fra de konkrete klasser den bruger. Factory Pattern fremmer loose coupling og gør din kode mere vedligeholdelig og testbar. I moderne PHP udvikling bruges Factory Pattern ofte sammen med dependency injection containers til at håndtere kompleks objekt instantiering.

Key Points

  • Afkobler objekt creation fra objekt brug
  • Centraliserer creation logic ét sted
  • Gør det nemt at udvide med nye typer
  • Klienten kender kun til interface, ikke konkrete klasser
  • Følger Open/Closed Principle
  • Kan reducere kodeduplikering ved objekt creation
  • Gør testing lettere ved at kunne mock factories
  • Simple Factory, Factory Method og Abstract Factory er varianter
  • Bruges ofte sammen med Dependency Injection
  • Særligt nyttigt når creation logic er kompleks
  • Tillader runtime beslutning om hvilken klasse der skal oprettes
  • Fremmer loose coupling mellem komponenter

Kode Eksempel

<?php

declare(strict_types=1);

namespace App\Notification;

enum NotificationType: string
{
    case EMAIL = 'email';
    case SMS = 'sms';
    case PUSH = 'push';
    case SLACK = 'slack';
}

interface NotificationInterface
{
    public function send(string $recipient, string $message): bool;
}

final readonly class EmailNotification implements NotificationInterface
{
    public function __construct(
        private string $smtpHost,
        private int $smtpPort
    ) {}
    
    public function send(string $recipient, string $message): bool
    {
        // Email sending logic
        echo "Sending email to {$recipient}: {$message}\n";
        echo "Via SMTP: {$this->smtpHost}:{$this->smtpPort}\n";
        return true;
    }
}

final readonly class SmsNotification implements NotificationInterface
{
    public function __construct(
        private string $apiKey,
        private string $provider
    ) {}
    
    public function send(string $recipient, string $message): bool
    {
        echo "Sending SMS to {$recipient}: {$message}\n";
        echo "Via {$this->provider} with API key\n";
        return true;
    }
}

final readonly class PushNotification implements NotificationInterface
{
    public function __construct(
        private string $deviceToken
    ) {}
    
    public function send(string $recipient, string $message): bool
    {
        echo "Sending push notification to {$recipient}: {$message}\n";
        return true;
    }
}

final readonly class SlackNotification implements NotificationInterface
{
    public function __construct(
        private string $webhookUrl
    ) {}
    
    public function send(string $recipient, string $message): bool
    {
        echo "Sending Slack message to {$recipient}: {$message}\n";
        echo "Via webhook: {$this->webhookUrl}\n";
        return true;
    }
}

/**
 * Factory Pattern implementation
 */
final class NotificationFactory
{
    public function __construct(
        private readonly array $config
    ) {}
    
    public function create(NotificationType $type): NotificationInterface
    {
        return match($type) {
            NotificationType::EMAIL => new EmailNotification(
                $this->config['email']['smtp_host'] ?? 'localhost',
                $this->config['email']['smtp_port'] ?? 587
            ),
            NotificationType::SMS => new SmsNotification(
                $this->config['sms']['api_key'] ?? '',
                $this->config['sms']['provider'] ?? 'Twilio'
            ),
            NotificationType::PUSH => new PushNotification(
                $this->config['push']['device_token'] ?? ''
            ),
            NotificationType::SLACK => new SlackNotification(
                $this->config['slack']['webhook_url'] ?? ''
            ),
        };
    }
}

// Brug af Factory Pattern
$config = [
    'email' => ['smtp_host' => 'smtp.gmail.com', 'smtp_port' => 587],
    'sms' => ['api_key' => 'xxx', 'provider' => 'Twilio'],
    'slack' => ['webhook_url' => 'https://hooks.slack.com/xxx'],
];

$factory = new NotificationFactory($config);

// Opretter forskellige notifikationer baseret på behov
$emailNotifier = $factory->create(NotificationType::EMAIL);
$emailNotifier->send('user@example.com', 'Welcome to our platform!');

$smsNotifier = $factory->create(NotificationType::SMS);
$smsNotifier->send('+4512345678', 'Your code is: 1234');

$slackNotifier = $factory->create(NotificationType::SLACK);
$slackNotifier->send('#general', 'Deployment completed successfully!');

Fordele

  • +Loose coupling - klienten afhænger kun af interface, ikke konkrete klasser
  • +Single Responsibility - creation logic er separeret fra business logic
  • +Open/Closed Principle - let at tilføje nye typer uden at ændre eksisterende kode
  • +Centraliseret creation logic gør vedligeholdelse lettere
  • +Forbedrer testbarhed ved at kunne mock factories
  • +Runtime fleksibilitet til at vælge konkrete implementationer

Ulemper

  • Kan introducere ekstra kompleksitet for simple use cases
  • Flere klasser skal oprettes og vedligeholdes
  • Kan gøre koden sværere at følge for nybegyndere
  • Over-engineering risiko hvis creation logic er triviel

Hvornår bruges det?

  • Notifikationssystemer - opret forskellige typer notifikationer baseret på brugerindstillinger
  • Payment processors - instantier forskellige payment gateways (Stripe, PayPal, etc.)
  • Database connections - opret forskellige database drivers (MySQL, PostgreSQL, SQLite)
  • File parsers - opret parsers baseret på filtype (JSON, XML, CSV)
  • Logger implementations - opret forskellige log handlers (file, database, cloud)
  • Report generators - generér forskellige rapportformater (PDF, Excel, HTML)

Best Practices

  • Brug interfaces til at definere kontrakter for produkter
  • Udnyt PHP 8+ match expressions for clean type mapping
  • Inject configuration dependencies gennem constructor
  • Gør factory klasser final for at forhindre uventet arv
  • Overvej Static Factory Methods for simple scenarier
  • Kombiner med Dependency Injection containers til større projekter
  • Dokumenter hvilke typer factory kan oprette
  • Brug enums for type-safe factory input

Quick Info

Kategori
Creational
Sværhedsgrad
Begynder-venlig
Relaterede Patterns
  • Abstract Factory - factory of factories til at oprette relaterede objekter
  • Builder Pattern - til kompleks objekt construction med mange parametre
  • Prototype Pattern - alternativ måde at oprette objekter ved kloning