Composition Over Inheritance: Crafting More Flexible PHP Code
Composition Over Inheritance
Introduction
Imagine you're building a LEGO set. Would you prefer a massive, pre-built structure that's hard to modify, or a collection of flexible blocks that you can rearrange and combine in countless ways? In the world of object-oriented programming, the "Composition over Inheritance" principle is just like choosing those versatile LEGO blocks over a rigid, fixed model.
What is Composition Over Inheritance?
At its core, composition is about building complex objects by combining simpler, more focused objects, rather than creating deep inheritance hierarchies. It's like creating a Swiss Army knife by bringing together different specialized tools, instead of trying to create one mega-tool that does everything.
The Problem with Traditional Inheritance
Let's look at a classic inheritance example that quickly becomes problematic:
class Vehicle {
public function startEngine() { /* ... */ }
public function stopEngine() { /* ... */ }
public function fly() { /* ... */ }
public function swim() { /* ... */ }
}
class Car extends Vehicle {
// Cars don't fly or swim, but they inherit these methods!
}
class Airplane extends Vehicle {
// Works for flying, but what about swimming?
}
This approach leads to several issues:
Inherited methods that don't make sense for all subclasses
Rigid structure that's hard to extend
Violation of the Liskov Substitution Principle
Composition: A Better Approach
Let's reimagine our vehicle design using composition:
interface EngineInterface {
public function start(): void;
public function stop(): void;
}
interface FlyingInterface {
public function fly(): void;
}
interface SwimmingInterface {
public function swim(): void;
}
class StandardEngine implements EngineInterface {
public function start(): void {
echo "Standard engine starting...";
}
public function stop(): void {
echo "Standard engine stopping...";
}
}
class JetEngine implements EngineInterface, FlyingInterface {
public function start(): void {
echo "Jet engine starting...";
}
public function stop(): void {
echo "Jet engine stopping...";
}
public function fly(): void {
echo "Jet flying high!";
}
}
class Vehicle {
private $engine;
private $flyingCapability;
private $swimmingCapability;
public function __construct(
EngineInterface $engine,
?FlyingInterface $flyingCapability = null,
?SwimmingInterface $swimmingCapability = null
) {
$this->engine = $engine;
$this->flyingCapability = $flyingCapability;
$this->swimmingCapability = $swimmingCapability;
}
public function start() {
$this->engine->start();
}
public function fly() {
if ($this->flyingCapability) {
$this->flyingCapability->fly();
} else {
throw new \Exception("This vehicle cannot fly");
}
}
}
// Real-world usage
$car = new Vehicle(new StandardEngine());
$airplane = new Vehicle(new JetEngine(), new JetEngine());
Real-Life Analogy: The Professional Toolkit
Think of composition like a professional's toolkit:
A carpenter doesn't become a carpenter by inheriting all skills from a "Craftsman" class
Instead, they collect specific tools (skills) that can be combined as needed
They can add or remove tools (capabilities) without redesigning their entire approach
Benefits of Composition
Flexibility: Easily add or remove behaviors
Modularity: Create complex objects from simple, focused components
Reduced Coupling: Objects depend on interfaces, not concrete implementations
Easier Testing: Swap out components more easily
When to Use Composition
When behaviors can change dynamically
When you want to avoid deep inheritance hierarchies
When you need to combine capabilities in multiple ways
When you want to follow the Single Responsibility Principle
Common Pitfalls to Avoid
Don't create overly complex composition structures
Keep interfaces small and focused
Avoid too many dependencies between components
Conclusion
Composition over inheritance is like cooking: instead of creating one massive, inflexible dish, you're preparing individual ingredients that can be mixed and matched to create endless culinary possibilities.
By embracing composition, you'll write more flexible, maintainable, and scalable PHP code that can adapt to changing requirements with ease.
Quick Takeaways
✅ Prefer combining behaviors over deep inheritance
✅ Use interfaces to define contracts
✅ Keep components small and focused
✅ Think of objects as collections of capabilities
Happy coding! 🚀📦
Subscribe to my newsletter
Read articles from Sohag Hasan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sohag Hasan
Sohag Hasan
WhoAmI => notes.sohag.pro/author