Abstract Factory Pattern
The Abstract Factory Pattern is a creational design pattern used to create families of related objects without exposing their concrete classes to the main application code. It is useful when an application needs to create multiple objects that belong together and must work as a consistent group.
This pattern is commonly used in software systems that support multiple themes, platforms, database drivers, UI components, payment providers, file exporters, or service families. Instead of creating each object separately, the Abstract Factory Pattern provides a factory interface that creates related objects through a common contract.
Introduction
In the previous article of this Design Patterns series, we discussed the Factory Pattern. The Factory Pattern helps create one type of object based on input, configuration, or business rules. It centralizes object creation and keeps business logic cleaner.
The Abstract Factory Pattern goes one step further. Instead of creating only one object, it creates a group or family of related objects. These objects are usually designed to work together and follow the same style, provider, platform, or environment.
For example, a user interface system may need to create buttons, checkboxes, and input fields. If the application supports both light theme and dark theme, each theme should provide its own matching components. Abstract Factory helps create all related components from the same family.
What Is the Abstract Factory Pattern?
The Abstract Factory Pattern is a design pattern that defines an interface for creating families of related objects without specifying their exact concrete classes.
In simple terms, it allows the application to ask for a group of objects from a factory, while the factory decides which concrete classes should be used.
The main application code does not need to know whether it is using a light theme, dark theme, MySQL driver, PostgreSQL driver, Stripe provider, or PayPal provider. It only depends on abstract interfaces.
Main Idea of Abstract Factory
The main idea of Abstract Factory is to separate the creation of related objects from the code that uses them. The pattern usually includes:
An abstract factory interface that defines methods for creating related objects.
Concrete factory classes that implement the abstract factory interface.
Product interfaces that define the behavior of created objects.
Concrete product classes that belong to specific families.
Client code that uses only factory and product interfaces.
This structure makes the system flexible because the application can switch between object families without changing the main business logic.
Why Abstract Factory Is Important
Abstract Factory is important because many applications need to work with related objects that must stay consistent. If these objects are created manually in different places, it becomes easy to mix incompatible classes.
For example, an application that supports multiple UI themes should not accidentally create a light theme button with a dark theme checkbox. A reporting system should not mix PDF-specific header components with Excel-specific body components. A payment system should not mix Stripe payment objects with PayPal refund objects.
Abstract Factory prevents this kind of inconsistency by creating related objects from the same factory family.
Problem Without Abstract Factory
Imagine an application that supports two design themes: light and dark. The application needs to create buttons and input fields for each theme.
Without Abstract Factory, the code may contain conditions like this:
if ($theme === 'light') {
$button = new LightButton();
$input = new LightInput();
} elseif ($theme === 'dark') {
$button = new DarkButton();
$input = new DarkInput();
}This may work in a small example, but the problem grows when the application has many components. If the system later adds cards, modals, menus, alerts, and tables, the conditional logic becomes larger and harder to maintain.
Abstract Factory moves this creation logic into dedicated factory classes and keeps the client code cleaner.
Abstract Factory Structure
A typical Abstract Factory structure contains several parts. First, product interfaces define common behavior for each type of product. Then, concrete products implement these interfaces. After that, an abstract factory interface defines methods for creating product families. Finally, concrete factories create matching products.
This may sound complex at first, but the idea is simple: one factory creates a consistent family of related objects.
Abstract Factory Example in PHP
The following example shows an Abstract Factory implementation for a theme-based UI system.
interface Button
{
public function render(): string;
}
interface Input
{
public function render(): string;
}
class LightButton implements Button
{
public function render(): string
{
return 'Rendering light button';
}
}
class LightInput implements Input
{
public function render(): string
{
return 'Rendering light input';
}
}
class DarkButton implements Button
{
public function render(): string
{
return 'Rendering dark button';
}
}
class DarkInput implements Input
{
public function render(): string
{
return 'Rendering dark input';
}
}In this example, Button and Input are product interfaces. LightButton, LightInput, DarkButton, and DarkInput are concrete products.
Creating the Abstract Factory Interface
Now we can define an abstract factory interface that creates related UI components:
interface UiFactory
{
public function createButton(): Button;
public function createInput(): Input;
}This interface does not know anything about light or dark themes. It only defines that any UI factory must be able to create a button and an input.
Creating Concrete Factories
Next, we create concrete factories for each theme:
class LightThemeFactory implements UiFactory
{
public function createButton(): Button
{
return new LightButton();
}
public function createInput(): Input
{
return new LightInput();
}
}
class DarkThemeFactory implements UiFactory
{
public function createButton(): Button
{
return new DarkButton();
}
public function createInput(): Input
{
return new DarkInput();
}
}Each factory creates a consistent family of objects. LightThemeFactory creates only light components, while DarkThemeFactory creates only dark components.
Using the Abstract Factory
The client code can now use the UiFactory interface without depending on concrete classes:
class PageRenderer
{
public function __construct(
private UiFactory $factory
) {
}
public function render(): string
{
$button = $this->factory->createButton();
$input = $this->factory->createInput();
return $button->render() . PHP_EOL . $input->render();
}
}
$factory = new DarkThemeFactory();
$page = new PageRenderer($factory);
echo $page->render();The PageRenderer class does not know whether the factory creates light components or dark components. It only depends on the UiFactory interface.
This makes the design flexible and easy to extend.
Switching Object Families
One of the biggest benefits of Abstract Factory is that the application can switch the entire object family by changing the factory.
For example:
$theme = 'light';
$factory = match ($theme) {
'light' => new LightThemeFactory(),
'dark' => new DarkThemeFactory(),
default => throw new InvalidArgumentException('Invalid theme.'),
};
$page = new PageRenderer($factory);If the theme changes, the application uses a different factory. The rest of the PageRenderer logic remains unchanged.
Abstract Factory vs Factory Pattern
The Factory Pattern and Abstract Factory Pattern are related, but they solve different design problems.
The Factory Pattern usually creates one type of object. For example, a PaymentFactory may create a CreditCardPayment, PayPalPayment, or BankTransferPayment object.
The Abstract Factory Pattern creates families of related objects. For example, a ThemeFactory may create a Button, Input, Modal, and Checkbox that all belong to the same theme.
In short, Factory focuses on creating a single product, while Abstract Factory focuses on creating related product families.
Main Differences Between Factory and Abstract Factory
The main differences are:
Factory Pattern: Creates one object from a group of possible classes.
Abstract Factory Pattern: Creates multiple related objects that belong to the same family.
Factory Pattern: Usually has one creation method.
Abstract Factory Pattern: Usually has multiple creation methods.
Factory Pattern: Useful for choosing one implementation.
Abstract Factory Pattern: Useful for keeping related implementations consistent.
Both patterns improve object creation, but Abstract Factory is more suitable when objects must be created together as a compatible group.
Real-World Example: Database Driver Family
Another practical example is a database system that supports different database engines. A MySQL family may include a connection object, query builder, and schema manager. A PostgreSQL family may provide different implementations of the same concepts.
Abstract Factory can ensure that all database-related objects come from the same database family.
interface Connection
{
public function connect(): string;
}
interface QueryBuilder
{
public function select(string $table): string;
}
interface DatabaseFactory
{
public function createConnection(): Connection;
public function createQueryBuilder(): QueryBuilder;
}Each concrete database factory can create matching objects for its database engine.
Database Factory Implementation
class MySqlConnection implements Connection
{
public function connect(): string
{
return 'Connecting to MySQL';
}
}
class MySqlQueryBuilder implements QueryBuilder
{
public function select(string $table): string
{
return 'SELECT * FROM `' . $table . '`';
}
}
class MySqlFactory implements DatabaseFactory
{
public function createConnection(): Connection
{
return new MySqlConnection();
}
public function createQueryBuilder(): QueryBuilder
{
return new MySqlQueryBuilder();
}
}A PostgreSQL factory could create PostgreSQL-specific connection and query builder objects. The application code can depend on DatabaseFactory without knowing the concrete database type.
Real-World Example: Payment Provider Family
Payment systems are another useful example. A payment provider may include several related services, such as payment processing, refund handling, subscription management, and invoice creation.
For example, a Stripe family may include StripePaymentProcessor, StripeRefundService, and StripeSubscriptionService. A PayPal family may include PayPalPaymentProcessor, PayPalRefundService, and PayPalSubscriptionService.
Abstract Factory can create the correct group of provider-specific services and prevent mixing services from different providers.
Payment Provider Abstract Factory Example
interface PaymentProcessor
{
public function pay(float $amount): bool;
}
interface RefundProcessor
{
public function refund(float $amount): bool;
}
interface PaymentProviderFactory
{
public function createPaymentProcessor(): PaymentProcessor;
public function createRefundProcessor(): RefundProcessor;
}
class StripeProviderFactory implements PaymentProviderFactory
{
public function createPaymentProcessor(): PaymentProcessor
{
return new StripePaymentProcessor();
}
public function createRefundProcessor(): RefundProcessor
{
return new StripeRefundProcessor();
}
}
class PayPalProviderFactory implements PaymentProviderFactory
{
public function createPaymentProcessor(): PaymentProcessor
{
return new PayPalPaymentProcessor();
}
public function createRefundProcessor(): RefundProcessor
{
return new PayPalRefundProcessor();
}
}This design allows the application to switch between payment providers while keeping all related services consistent.
Benefits of Abstract Factory Pattern
The Abstract Factory Pattern provides many benefits when an application needs related object families.
Main benefits include:
Creates families of related objects consistently.
Prevents mixing incompatible objects from different families.
Separates object creation from business logic.
Supports interface-based design and polymorphism.
Makes switching between families easier.
Improves maintainability in large systems.
Supports the Open Closed Principle when designed carefully.
These benefits make Abstract Factory useful in systems that support multiple providers, themes, platforms, environments, or product families.
Drawbacks of Abstract Factory Pattern
Although Abstract Factory is powerful, it can also increase complexity. It usually requires multiple interfaces, multiple concrete classes, and several factory classes.
This can be unnecessary for small projects or simple object creation. If the application does not need to create related object families, a simple factory or direct object creation may be better.
Another drawback is that adding a new product type to every family can require changes in all factory interfaces and concrete factories. For example, if a UI factory creates buttons and inputs, then adding a new createModal method requires updating all theme factories.
When to Use Abstract Factory Pattern
Use Abstract Factory when your application needs to create families of related objects and those objects must be used together consistently.
Abstract Factory is useful when:
The application supports multiple themes or platforms.
Several related objects must come from the same family.
The code should not depend on concrete classes.
The system needs to switch between complete object families.
You want to prevent incompatible objects from being mixed.
Object creation rules are complex and should be centralized.
If these conditions exist, Abstract Factory can improve the design and make the system easier to extend.
When Not to Use Abstract Factory Pattern
Do not use Abstract Factory when the object creation problem is simple. If the application only creates one object type, the Factory Pattern may be enough. If the object creation is direct and unlikely to change, no factory may be needed.
Avoid Abstract Factory when:
There is only one product type.
There are no related object families.
The pattern adds too many classes without real benefit.
The project is small and simple.
Direct dependency injection already solves the problem cleanly.
Design patterns should reduce long-term complexity, not create unnecessary structure.
Abstract Factory and Dependency Injection
Abstract Factory works well with dependency injection. Instead of creating a concrete factory directly inside the client class, the factory can be injected through the constructor.
This makes the client code more testable and flexible.
class CheckoutService
{
public function __construct(
private PaymentProviderFactory $factory
) {
}
public function checkout(float $amount): bool
{
$processor = $this->factory->createPaymentProcessor();
return $processor->pay($amount);
}
}The CheckoutService depends on the PaymentProviderFactory interface. During runtime, the application can inject StripeProviderFactory, PayPalProviderFactory, or another provider factory.
Abstract Factory in Laravel
In Laravel, Abstract Factory can be used when an application supports multiple service families. For example, a project may support several payment providers, export formats, notification providers, or storage systems.
Laravel's service container can help inject the correct factory based on configuration. Instead of manually choosing concrete classes across the application, a service provider can bind the correct factory implementation.
$this->app->bind(
PaymentProviderFactory::class,
StripeProviderFactory::class
);Now any service that depends on PaymentProviderFactory can receive the configured factory automatically.
Abstract Factory in Symfony
Symfony also supports Abstract Factory through services, dependency injection, and configuration. Developers can define different factory services and inject the correct one depending on environment, configuration, or business rules.
This approach is useful in large Symfony applications where service families must be switched without changing business classes.
Abstract Factory and Clean Architecture
Abstract Factory can support clean architecture when it helps keep high-level business logic independent from low-level implementation details.
For example, the business logic can depend on a PaymentProviderFactory interface instead of depending directly on Stripe or PayPal classes. This keeps the core application logic more stable and easier to test.
However, Abstract Factory should not be used everywhere. It should be used when the system truly needs interchangeable families of related objects.
Abstract Factory and Open Closed Principle
The Abstract Factory Pattern can support the Open Closed Principle by allowing new object families to be added without changing the client code.
For example, if an application already supports LightThemeFactory and DarkThemeFactory, a new HighContrastThemeFactory can be added. The PageRenderer class does not need to change because it depends on the UiFactory interface.
This makes the system open for new families while keeping existing client code closed for modification.
Common Mistakes with Abstract Factory
One common mistake is using Abstract Factory for simple object creation. If there is no family of related objects, Abstract Factory may be too heavy.
Another mistake is making product families unclear. All products created by the same factory should belong together logically. If the factory creates unrelated objects, the design becomes confusing.
A third mistake is placing business logic inside the factory. The factory should focus on object creation. Business workflows should remain in services, domain classes, or use cases.
A fourth mistake is creating too many abstractions too early. It is better to introduce Abstract Factory when the need becomes clear.
Best Practices for Abstract Factory Pattern
To use Abstract Factory correctly, developers should keep the design focused and meaningful.
Useful best practices include:
Use Abstract Factory only for related object families.
Define clear product interfaces.
Keep concrete product classes consistent within each family.
Keep factories focused on creation logic.
Use dependency injection to provide factories to client classes.
Avoid adding business workflows inside factories.
Use meaningful names such as ThemeFactory, DatabaseFactory, or PaymentProviderFactory.
Do not introduce the pattern if a simple factory is enough.
These practices help keep the Abstract Factory Pattern useful and understandable.
Practical Checklist Before Using Abstract Factory
Before using Abstract Factory, ask these questions:
Do I need to create multiple related objects?
Do these objects belong to different families or providers?
Should the application switch between complete families?
Is it important to prevent mixing incompatible objects?
Will client code become cleaner by depending on interfaces?
Would a simple Factory Pattern be enough?
Will this design make future changes easier?
If the answer is yes to most of these questions, Abstract Factory may be a good design choice.
Conclusion
The Abstract Factory Pattern is a creational design pattern used to create families of related objects without exposing concrete classes to the client code. It is especially useful when an application supports multiple themes, platforms, providers, database engines, or service families.
Compared with the Factory Pattern, Abstract Factory is more focused on creating groups of compatible objects. It improves consistency, supports polymorphism, reduces coupling, and helps keep business logic independent from object creation details.
However, Abstract Factory should be used carefully. It can add complexity when the problem is simple. Developers should apply it when the application truly needs interchangeable families of related objects. When used correctly, it is a powerful pattern for building flexible, scalable, and maintainable object-oriented software.

