PHP / Magento Dev Blog

  • Publikacje
  • O autorze
  • Kontakt

Factory Method + Abstract Factory – implementacje od zera, tabela różnic, Simple Factory jako alternatywa

by Henryk Tews / wtorek, 10 maja 2022 / Opublikowano w Wzorce projektowe

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.

About Henryk Tews

Co możesz przeczytać następne

Wzorzec Memento – undo/redo, historia zmian cen, persystencja do bazy jako audit log
Wzorce GoF w Magento 2 – gdzie je znaleźć i jak działają
Decorator i Proxy w PHP – wzorce strukturalne
  • Publikacje
  • O autorze
  • Kontakt

© 2026 Created by

GÓRA
Zarządzaj zgodą
Aby zapewnić jak najlepsze wrażenia, korzystamy z technologii, takich jak pliki cookie, do przechowywania i/lub uzyskiwania dostępu do informacji o urządzeniu. Zgoda na te technologie pozwoli nam przetwarzać dane, takie jak zachowanie podczas przeglądania lub unikalne identyfikatory na tej stronie. Brak wyrażenia zgody lub wycofanie zgody może niekorzystnie wpłynąć na niektóre cechy i funkcje.
Funkcjonalne Zawsze aktywne
Przechowywanie lub dostęp do danych technicznych jest ściśle konieczny do uzasadnionego celu umożliwienia korzystania z konkretnej usługi wyraźnie żądanej przez subskrybenta lub użytkownika, lub wyłącznie w celu przeprowadzenia transmisji komunikatu przez sieć łączności elektronicznej.
Preferencje
Przechowywanie lub dostęp techniczny jest niezbędny do uzasadnionego celu przechowywania preferencji, o które nie prosi subskrybent lub użytkownik.
Statystyka
Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do celów statystycznych. Przechowywanie techniczne lub dostęp, który jest używany wyłącznie do anonimowych celów statystycznych. Bez wezwania do sądu, dobrowolnego podporządkowania się dostawcy usług internetowych lub dodatkowych zapisów od strony trzeciej, informacje przechowywane lub pobierane wyłącznie w tym celu zwykle nie mogą być wykorzystywane do identyfikacji użytkownika.
Marketing
Przechowywanie lub dostęp techniczny jest wymagany do tworzenia profili użytkowników w celu wysyłania reklam lub śledzenia użytkownika na stronie internetowej lub na kilku stronach internetowych w podobnych celach marketingowych.
  • Zarządzaj opcjami
  • Zarządzaj serwisami
  • Zarządzaj {vendor_count} dostawcami
  • Przeczytaj więcej o tych celach
Zobacz preferencje
  • {title}
  • {title}
  • {title}