Design patterns: 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. This pattern is useful when you want to make sure that a set of objects belongs to a given family and is used consistently.
Characteristics of the Abstract Factory pattern
- Creating families of objects: It lets you create groups of objects that belong to a given family or context, ensuring they are compatible with one another.
- Abstract interface: It provides an abstract interface that defines the methods for creating the objects. Concrete classes implement this interface to supply the specific creation logic.
- Independence from concrete classes: The client is unaware of the concrete classes being created. It can use the abstract factory’s interface without worrying about how the concrete classes are implemented.
- Easier extension: Adding new products or product families becomes easier. You just create a new class that implements the abstract interface, without modifying existing code.
- Consistency: It ensures that the objects created are consistent with one another. For example, when building a user interface, you can generate elements such as buttons and windows that belong to the same visual theme.
- Support for variability: It lets you change the family of objects being created without modifying the client code. You can switch to a different abstract factory implementation and get completely different objects.
Example in PHP:
Let’s imagine we have two types of car, each with two components: Seats and Engine. We can create two families of cars: Luxury Car and Sports Car.
// Interface for seats
interface Seats {
public function createSeats();
}
// Interface for engines
interface Engine {
public function createEngine();
}
// Seats for luxury cars
class LuxurySeats implements Seats {
public function createSeats() {
return "Luxury seats";
}
}
// Seats for sports cars
class SportSeats implements Seats {
public function createSeats() {
return "Sports seats";
}
}
// Engine for luxury cars
class LuxuryEngine implements Engine {
public function createEngine() {
return "Luxury engine";
}
}
// Engine for sports cars
class SportEngine implements Engine {
public function createEngine() {
return "Sports engine";
}
}
// Abstract Factory
interface CarFactory {
public function createSeats(): Seats;
public function createEngine(): Engine;
}
// Factory for luxury cars
class LuxuryCarFactory implements CarFactory {
public function createSeats(): Seats {
return new LuxurySeats();
}
public function createEngine(): Engine {
return new LuxuryEngine();
}
}
// Factory for sports cars
class SportCarFactory implements CarFactory {
public function createSeats(): Seats {
return new SportSeats();
}
public function createEngine(): Engine {
return new SportEngine();
}
}
// Usage
function buildCar(CarFactory $factory) {
$seats = $factory->createSeats();
$engine = $factory->createEngine();
echo $seats->createSeats() . " with " . $engine->createEngine() . "\n";
}
$factoryLuxury = new LuxuryCarFactory();
$factorySport = new SportCarFactory();
buildCar($factoryLuxury); // Luxury seats with Luxury engine
buildCar($factorySport); // Sports seats with Sports engine
Common use case
A classic example is an application that manages user interfaces for different operating systems (Windows, macOS, Linux). The abstract factory can provide methods for creating components such as windows, buttons and menus, ensuring that all the objects are designed to work together and follow the style of the specified operating system.