Factory Method i Abstract Factory to dwa najczęściej mylone wzorce kreacyjne. Oba dotyczą tworzenia obiektów, ale rozwiązują różne problemy. Factory Method pyta „który podtyp obiektu stworzyć”. Abstract Factory pyta „jaką rodzinę powiązanych obiektów stworzyć”. Pokazuję oba od zera w PHP z przykładami które ilustrują różnicę.
Factory Method
Wzorzec definiuje interfejs do tworzenia obiektu, ale pozwala podklasom zdecydować jaką klasę instancjonować. Klasa bazowa wie że obiekt ma być stworzony, podklasy wiedzą jaki.
<?php
declare(strict_types=1);
// Produkt - interfejs obiektu który fabryka tworzy
interface NotificationInterface
{
public function send(string $recipient, string $message): void;
}
// Konkretne produkty
class EmailNotification implements NotificationInterface
{
public function send(string $recipient, string $message): void
{
echo "Email do {$recipient}: {$message}" . PHP_EOL;
}
}
class SmsNotification implements NotificationInterface
{
public function send(string $recipient, string $message): void
{
echo "SMS do {$recipient}: {$message}" . PHP_EOL;
}
}
class SlackNotification implements NotificationInterface
{
public function send(string $recipient, string $message): void
{
echo "Slack @{$recipient}: {$message}" . PHP_EOL;
}
}
<?php
// Creator - klasa bazowa z factory method
abstract class NotificationSender
{
// Factory method - podklasy implementują
abstract protected function createNotification(): NotificationInterface;
// Logika biznesowa używa obiektu bez wiedzy o konkretnym typie
public function notify(string $recipient, string $message): void
{
$notification = $this->createNotification();
// Można tu dodać logikę wspólną dla wszystkich - logowanie, retry itd.
echo "Wysyłam powiadomienie..." . PHP_EOL;
$notification->send($recipient, $message);
}
}
// Concrete Creators - każdy tworzy inny produkt
class EmailSender extends NotificationSender
{
protected function createNotification(): NotificationInterface
{
return new EmailNotification();
}
}
class SmsSender extends NotificationSender
{
protected function createNotification(): NotificationInterface
{
return new SmsNotification();
}
}
class SlackSender extends NotificationSender
{
protected function createNotification(): NotificationInterface
{
return new SlackNotification();
}
}
// Użycie - klient pracuje z NotificationSender, nie zna konkretnego typu
function sendAlert(NotificationSender $sender, string $contact): void
{
$sender->notify($contact, 'Uwaga: wykryto problem w systemie');
}
sendAlert(new EmailSender(), 'admin@example.com');
sendAlert(new SmsSender(), '+48123456789');
sendAlert(new SlackSender(), 'devops-team');
Wariant z prostą fabryką statyczną – mniej OOP, bardziej pragmatyczny, wystarczający w wielu przypadkach:
<?php
// Simple Factory - nie jest GoF ale często wystarczy
class NotificationFactory
{
public static function create(string $type): NotificationInterface
{
return match($type) {
'email' => new EmailNotification(),
'sms' => new SmsNotification(),
'slack' => new SlackNotification(),
default => throw new \InvalidArgumentException("Unknown type: {$type}"),
};
}
}
$notification = NotificationFactory::create('email');
$notification->send('jan@example.com', 'Wiadomość testowa');
Abstract Factory
Abstract Factory tworzy rodziny powiązanych obiektów. Gwarantuje że obiekty z tej samej rodziny są kompatybilne ze sobą.
Przykład: aplikacja która obsługuje dwa motywy UI – jasny i ciemny. Każdy motyw ma spójny zestaw komponentów: przycisk, input, okno dialogowe. Abstract Factory zapewnia że nie pomieszasz komponentów z różnych motywów:
<?php
declare(strict_types=1);
// Abstrakcyjne produkty - interfejsy dla każdego rodzaju komponentu
interface ButtonInterface
{
public function render(): string;
public function onClick(): void;
}
interface InputInterface
{
public function render(): string;
public function getValue(): string;
}
interface DialogInterface
{
public function render(): string;
public function show(): void;
}
<?php
// Konkretne produkty - wersja jasna
class LightButton implements ButtonInterface
{
public function render(): string { return '<button class="btn-light">Kliknij</button>'; }
public function onClick(): void { echo "Light button clicked\n"; }
}
class LightInput implements InputInterface
{
private string $value = '';
public function render(): string { return '<input class="input-light" />'; }
public function getValue(): string { return $this->value; }
}
class LightDialog implements DialogInterface
{
public function render(): string { return '<div class="dialog-light">...</div>'; }
public function show(): void { echo "Light dialog shown\n"; }
}
// Konkretne produkty - wersja ciemna
class DarkButton implements ButtonInterface
{
public function render(): string { return '<button class="btn-dark">Kliknij</button>'; }
public function onClick(): void { echo "Dark button clicked\n"; }
}
class DarkInput implements InputInterface
{
private string $value = '';
public function render(): string { return '<input class="input-dark" />'; }
public function getValue(): string { return $this->value; }
}
class DarkDialog implements DialogInterface
{
public function render(): string { return '<div class="dialog-dark">...</div>'; }
public function show(): void { echo "Dark dialog shown\n"; }
}
<?php
// Abstract Factory - interfejs fabryki rodziny komponentów
interface UIFactoryInterface
{
public function createButton(): ButtonInterface;
public function createInput(): InputInterface;
public function createDialog(): DialogInterface;
}
// Konkretne fabryki - każda tworzy kompletną, spójną rodzinę
class LightThemeFactory implements UIFactoryInterface
{
public function createButton(): ButtonInterface { return new LightButton(); }
public function createInput(): InputInterface { return new LightInput(); }
public function createDialog(): DialogInterface { return new LightDialog(); }
}
class DarkThemeFactory implements UIFactoryInterface
{
public function createButton(): ButtonInterface { return new DarkButton(); }
public function createInput(): InputInterface { return new DarkInput(); }
public function createDialog(): DialogInterface { return new DarkDialog(); }
}
// Aplikacja - korzysta tylko z interfejsów, nie zna konkretnych klas
class Application
{
private ButtonInterface $button;
private InputInterface $input;
private DialogInterface $dialog;
public function __construct(UIFactoryInterface $factory)
{
// Fabryka gwarantuje że wszystkie komponenty są z tej samej rodziny
$this->button = $factory->createButton();
$this->input = $factory->createInput();
$this->dialog = $factory->createDialog();
}
public function render(): void
{
echo $this->button->render() . PHP_EOL;
echo $this->input->render() . PHP_EOL;
echo $this->dialog->render() . PHP_EOL;
}
}
// Zmiana motywu = podmiana fabryki, zero zmian w Application
$lightApp = new Application(new LightThemeFactory());
$lightApp->render();
$darkApp = new Application(new DarkThemeFactory());
$darkApp->render();
Factory Method vs Abstract Factory – kluczowe różnice
| Aspekt | Factory Method | Abstract Factory |
|---|---|---|
| Tworzy | Jeden produkt | Rodzinę powiązanych produktów |
| Mechanizm | Dziedziczenie – podklasy nadpisują metodę | Kompozycja – wstrzykujesz fabrykę |
| Rozszerzanie | Nowa podklasa Creatora | Nowa implementacja fabryki |
| Kiedy używać | Nie wiesz z góry jakiego typu obiekt tworzyć | System musi być niezależny od sposobu tworzenia rodzin obiektów |
| Złożoność | Mniejsza | Większa – więcej interfejsów i klas |
Kiedy który wzorzec?
Factory Method gdy masz jedną hierarchię produktów i podklasy powinny decydować co tworzyć. Abstract Factory gdy masz kilka hierarchii produktów które muszą być spójne ze sobą – jak motywy UI, sterowniki baz danych z powiązanymi komponentami, czy zestawy produktów dla różnych platform.
Jeśli nie potrzebujesz elastyczności – Simple Factory (statyczna metoda z match/switch) jest wystarczająca i prostsza. Nie używaj Factory Method tylko dlatego że „tak się powinno robić”.
Podsumowanie
Factory Method i Abstract Factory rozwiązują problem tworzenia obiektów bez twardego sprzężenia z konkretnymi klasami. Factory Method robi to przez dziedziczenie, Abstract Factory przez kompozycję. W praktyce Abstract Factory pojawia się rzadziej – jego złożoność jest uzasadniona tylko gdy naprawdę zarządzasz rodzinami powiązanych obiektów które muszą być ze sobą kompatybilne.
