
In Object-Oriented Programming (OOP), there’s a popular design principle: “Favor composition over inheritance.” But what does that mean, and why should you care?
What is Composition?
Composition is an OOP technique where one class contains instances of other classes to reuse their functionality, rather than inheriting from them.
In simpler terms:
-
Inheritance is an “is-a” relationship.
-
Composition is a “has-a” relationship.
Let’s walk through an example to clarify this.
🏗️ Inheritance Example (Traditional Approach):
class Bird { public function fly() { echo "Bird flying\n"; } } class Eagle extends Bird { public function fly() { echo "Eagle flying\n"; } } $eagle = new Eagle(); $eagle->fly(); // Inherited from Bird
With inheritance: What if you want to use a different bird or reuse Bird
in something that’s not a Eagle
? You’re tightly coupled to the class hierarchy.
✅ Composition Example (Improved Design)
class Bird { public function fly() { echo "Bird flying\n"; } } class Eagle { private $bird; public function __construct(Bird $bird) { $this->bird = $bird; } public function fly() { $this->bird->flying(); echo "Eagle is flying\n"; } } $bird = new Bird(); $eagle = new Eagle($bird); $eagle->fly();
Benefits of Composition:
-
Greater flexibility (you can switch out actors).
-
Encourages separation of concerns.
-
Easier to test and maintain.
🎯 Real-World Use Case: Logging Service
Let’s say you want to add logging to various parts of your app.
Without Composition (Inheritance):
class BaseLogger { public function log($message) { echo "[LOG]: $message\n"; } } class UserService extends BaseLogger { public function createUser($name) { $this->log("User '$name' created."); } }
Now UserService
is stuck with BaseLogger
and can’t use another logger.
With Composition:
class Logger { public function log($message) { echo "[LOG]: $message\n"; } } class UserService { private $logger; public function __construct(Logger $logger) { $this->logger = $logger; } public function createUser($name) { $this->logger->log("User '$name' created."); } }
Now you can pass any logger that implements the same interface — even a FileLogger
, DatabaseLogger
, or NullLogger
for testing.
🔄 Composition with Interfaces:
To make your code even more flexible, use interfaces:
interface LoggerInterface { public function log($message); } class FileLogger implements LoggerInterface { public function log($message) { file_put_contents('app.log', $message . PHP_EOL, FILE_APPEND); } } class UserService { private $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } public function createUser($name) { $this->logger->log("User '$name' created."); } }
Now UserService
depends on an abstraction, not a concrete class.
🧠 Summary
-
Composition allows you to build flexible, testable, and maintainable code.
-
It helps you avoid deep inheritance hierarchies.
-
It promotes “has-a” relationships using class instances or interfaces.
-
You can swap components easily.