Factory Method and Abstract Factory are both creational patterns that remove the new keyword from business logic – but they solve different problems. Factory Method delegates creation to subclasses. Abstract Factory creates families of related objects. I implement both from scratch, compare them in a table, and show where Simple Factory fits as a pragmatic alternative.
Factory Method – subclass decides what to create
<?php
declare(strict_types=1);
// The "factory method" is an abstract method in the base class
abstract class Notification
{
// Factory Method - subclasses define how to create the sender
abstract protected function createSender(): SenderInterface;
// Template method uses the factory method
public function send(string $recipient, string $message): void
{
$sender = $this->createSender();
$sender->send($recipient, $message);
}
}
interface SenderInterface
{
public function send(string $recipient, string $message): void;
}
class EmailNotification extends Notification
{
protected function createSender(): SenderInterface
{
return new SmtpSender(host: 'smtp.example.com', port: 587);
}
}
class SmsNotification extends Notification
{
protected function createSender(): SenderInterface
{
return new TwilioSender(accountSid: 'ACxxxx', authToken: 'xxxx');
}
}
// Client code works with Notification - doesn't know which sender is used
function notifyCustomer(Notification $notification, string $email): void
{
$notification->send($email, 'Your order is ready.');
}
notifyCustomer(new EmailNotification(), 'customer@example.com');
notifyCustomer(new SmsNotification(), '+48600100200');
Abstract Factory – families of related objects
<?php
declare(strict_types=1);
// Abstract Factory creates a FAMILY of related objects
// Here: UI components for different themes
interface ButtonInterface { public function render(): string; }
interface FormInterface { public function render(): string; }
interface TableInterface { public function render(): string; }
// Abstract Factory - interface for creating the whole family
interface UiComponentFactory
{
public function createButton(string $label): ButtonInterface;
public function createForm(array $fields): FormInterface;
public function createTable(array $columns): TableInterface;
}
// Concrete factory 1: Bootstrap theme components
class BootstrapUiFactory implements UiComponentFactory
{
public function createButton(string $label): ButtonInterface
{
return new BootstrapButton($label);
}
public function createForm(array $fields): FormInterface
{
return new BootstrapForm($fields);
}
public function createTable(array $columns): TableInterface
{
return new BootstrapTable($columns);
}
}
// Concrete factory 2: Tailwind theme components
class TailwindUiFactory implements UiComponentFactory
{
public function createButton(string $label): ButtonInterface
{
return new TailwindButton($label);
}
public function createForm(array $fields): FormInterface
{
return new TailwindForm($fields);
}
public function createTable(array $columns): TableInterface
{
return new TailwindTable($columns);
}
}
// Application uses the factory without knowing the concrete classes
class AdminPanel
{
public function __construct(private UiComponentFactory $ui) {}
public function render(): string
{
$table = $this->ui->createTable(['Name', 'SKU', 'Price']);
$btn = $this->ui->createButton('Add Product');
return $table->render() . $btn->render();
}
}
// Swap the entire theme by changing one line
$panel = new AdminPanel(new TailwindUiFactory());
echo $panel->render();
Simple Factory – the pragmatic alternative
<?php
// Simple Factory is not a GoF pattern - but it is often the right choice
// Use it when you do not need subclass polymorphism or object families
class NotifierFactory
{
public function create(string $channel): SenderInterface
{
return match($channel) {
'email' => new SmtpSender(),
'sms' => new TwilioSender(),
'slack' => new SlackSender(),
default => throw new \InvalidArgumentException("Unknown channel: {$channel}"),
};
}
}
// With DI for extensibility - Magento 2 style
class ExtensibleNotifierFactory
{
public function __construct(
private array $senders // injected via di.xml
) {}
public function create(string $channel): SenderInterface
{
return $this->senders[$channel]
?? throw new \InvalidArgumentException("Unknown channel: {$channel}");
}
}
Comparison table
| Aspect | Simple Factory | Factory Method | Abstract Factory |
|---|---|---|---|
| GoF pattern? | No | Yes | Yes |
| Mechanism | Single class | Inheritance | Composition |
| Creates | One product type | One product type | Family of products |
| Extension | Modify factory | New subclass | New factory class |
| Complexity | Low | Medium | High |
| When to use | Simple creation logic | Polymorphic creation | UI kits, cross-platform code |
Summary
Start with Simple Factory. Upgrade to Factory Method when you find yourself extending the factory class hierarchy rather than modifying it. Use Abstract Factory when you need to ensure consistent object families – theme components, database drivers, platform-specific implementations. In Magento 2 the auto-generated *Factory classes are Simple Factories with DI-injected configuration, giving extensibility without full Abstract Factory complexity.
