Abstract Factory
The Abstract Factory Pattern is a creational design pattern that
provides an interface for creating families of related or dependent objects
without specifying their concrete classes.
It allows a client to create compatible products that belong to the same family.
Structure
| Role |
Example |
Responsibility |
| Abstract Factory |
NotificationFactory |
Declares methods for creating each product type |
| Concrete Factory |
EmailProvider, SmsProvider |
Creates a specific family of related products |
| Abstract Product |
MessageSender, MessageFormatter |
Defines the interface for product types |
| Concrete Product |
EmailSender, EmailFormatter, SMSSender, SMSFormatter |
Implements product variants |
Steps
- Create Abstract Product interfaces
- Create Concrete Products that implement these interfaces
- Create an Abstract Factory interface
- Implement Concrete Factories that produce product families
classDiagram
class NotificationFactory {
<<interface>>
+createSender()
+createFormatter()
}
class EmailNotificationFactory {
+createSender()
+createFormatter()
}
class SmsNotificationFactory {
+createSender()
+createFormatter()
}
class MessageSender {
<<interface>>
+send(message)
}
class MessageFormatter {
<<interface>>
+format(message)
}
class EmailSender
class SMSSender
class EmailFormatter
class SMSFormatter
EmailNotificationFactory ..|> NotificationFactory
SmsNotificationFactory ..|> NotificationFactory
EmailSender ..|> MessageSender
SMSSender ..|> MessageSender
EmailFormatter ..|> MessageFormatter
SMSFormatter ..|> MessageFormatter
EmailNotificationFactory --> EmailSender
EmailNotificationFactory --> EmailFormatter
SmsNotificationFactory --> SMSSender
SmsNotificationFactory --> SMSFormatter
The client works only with the factory interface (like NotificationFactory) and product interfaces (like MessageSender, MessageFormatter),
making it easy to switch entire product families without changing application code.
Example 1: Notification System
NotificationFactory creates families of:
MessageSender + MessageFormatter
Switching from Email to SMS delivery requires changing only the factory implementation.
Abstract Factory Interface
| NotificationFactory.php |
|---|
| <?php
declare(strict_types=1);
namespace DesignPattern\Creational\AbstractFactory\Notification\NotificationFactory;
use DesignPattern\Creational\AbstractFactory\Notification\MessageFormatter;
use DesignPattern\Creational\AbstractFactory\Notification\MessageSender;
interface NotificationFactory
{
public function createFormatter(): MessageFormatter;
public function createSender(): MessageSender;
}
|
Product Interfaces
Concrete Factories & Products
Tests
| NotificationTest.php |
|---|
| <?php
declare(strict_types=1);
namespace Tests\Creational\AbstractFactory\Notification;
use DesignPattern\Creational\AbstractFactory\Notification\EmailProvider\EmailFormatter;
use DesignPattern\Creational\AbstractFactory\Notification\SmsProvider\SmsFormatter;
use DesignPattern\Creational\AbstractFactory\Notification\NotificationFactory\EmailNotificationFactory;
use DesignPattern\Creational\AbstractFactory\Notification\NotificationFactory\SmsNotificationFactory;
use DesignPattern\Creational\AbstractFactory\Notification\NotificationFactory\NotificationFactory;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
#[CoversClass(NotificationFactory::class)]
class NotificationTest extends TestCase
{
/**
* @return array<int, mixed>
*/
public static function notificationFactoryProvider(): array
{
return [
[new EmailNotificationFactory()],
[new SmsNotificationFactory()],
];
}
public function testEmailFormatter(): void
{
$formatter = new EmailFormatter();
$data = [
'subject' => 'Hello',
'body' => 'World',
];
$result = $formatter->format($data);
self::assertSame("Subject: Hello\n\nWorld", $result);
}
public function testSmsFormatter(): void
{
$formatter = new SmsFormatter();
$data = [
'subject' => 'Hello',
'body' => 'World',
];
$result = $formatter->format($data);
self::assertSame('Hello: World', $result);
}
#[DataProvider('notificationFactoryProvider')]
public function testFormatter(NotificationFactory $factory): void
{
$formatter = $factory->createFormatter();
$data = [
'subject' => 'Order',
'body' => 'Your order shipped',
];
$result = $formatter->format($data);
self::assertStringContainsString('Order', $result);
self::assertStringContainsString('Your order shipped', $result);
}
#[DataProvider('notificationFactoryProvider')]
public function testSender(NotificationFactory $factory): void
{
$sender = $factory->createSender();
self::expectOutputRegex('/Sending (EMAIL|SMS) to/');
$sender->send('user@example.com', 'Test message');
}
}
|
Example 2: UI Theme System
ThemeFactory creates families of UI components:
Button + Checkbox
All components produced by one factory are guaranteed to be visually consistent.
Abstract Factory Interface
| ThemeFactory.php |
|---|
| <?php
declare(strict_types=1);
namespace DesignPattern\Creational\AbstractFactory\Theme\ThemeFactory;
use DesignPattern\Creational\AbstractFactory\Theme\Button;
use DesignPattern\Creational\AbstractFactory\Theme\Checkbox;
interface ThemeFactory
{
public function createButton(): Button;
public function createCheckbox(): Checkbox;
}
|
Product Interfaces
Concrete Factories & Products
Tests
| ThemeTest.php |
|---|
| <?php
declare(strict_types=1);
namespace Tests\Creational\AbstractFactory\Theme;
use DesignPattern\Creational\AbstractFactory\Theme\ThemeFactory\ThemeFactory;
use DesignPattern\Creational\AbstractFactory\Theme\DarkTheme\DarkButton;
use DesignPattern\Creational\AbstractFactory\Theme\DarkTheme\DarkCheckbox;
use DesignPattern\Creational\AbstractFactory\Theme\ThemeFactory\DarkThemeFactory;
use DesignPattern\Creational\AbstractFactory\Theme\ThemeFactory\LightThemeFactory;
use DesignPattern\Creational\AbstractFactory\Theme\LightTheme\LightButton;
use DesignPattern\Creational\AbstractFactory\Theme\LightTheme\LightCheckbox;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
#[CoversClass(ThemeFactory::class)]
class ThemeTest extends TestCase
{
/**
* @return array<int, array{0: ThemeFactory}>
*/
public static function themeFactoryProvider(): array
{
return [
[new LightThemeFactory()],
[new DarkThemeFactory()],
];
}
public function testLightButton(): void
{
$button = new LightButton();
self::assertSame(
'Rendering a LIGHT themed button.',
$button->render()
);
}
public function testDarkButton(): void
{
$button = new DarkButton();
self::assertSame(
'Rendering a DARK themed button.',
$button->render()
);
}
public function testLightCheckbox(): void
{
$checkbox = new LightCheckbox();
self::assertSame(
'Rendering a LIGHT themed checkbox.',
$checkbox->render()
);
}
public function testDarkCheckbox(): void
{
$checkbox = new DarkCheckbox();
self::assertSame(
'Rendering a DARK themed checkbox.',
$checkbox->render()
);
}
#[DataProvider('themeFactoryProvider')]
public function testButton(ThemeFactory $factory): void
{
$button = $factory->createButton();
$result = $button->render();
self::assertStringContainsString('button', $result);
}
#[DataProvider('themeFactoryProvider')]
public function testCheckbox(ThemeFactory $factory): void
{
$checkbox = $factory->createCheckbox();
$result = $checkbox->render();
self::assertStringContainsString('checkbox', $result);
}
#[DataProvider('themeFactoryProvider')]
public function testThemeConsistency(ThemeFactory $factory): void
{
$button = $factory->createButton();
$checkbox = $factory->createCheckbox();
$buttonRender = $button->render();
$checkboxRender = $checkbox->render();
if (str_contains($buttonRender, 'LIGHT')) {
self::assertStringContainsString('LIGHT', $checkboxRender);
}
if (str_contains($buttonRender, 'DARK')) {
self::assertStringContainsString('DARK', $checkboxRender);
}
}
}
|