When a subclass inherits from a super class, the subclass will gain knowledge and access to all of the super classes attributes and methods as long as their access modifiers are not private.
This means that if you have multiple levels of inheritance, a subclass at the bottom of the inheritance tree can potentially have a way to access attributes and behaviors of all the super classes.
It’s “bad” not because inheritance is wrong, but because deep or excessive inheritance creates several structural problems in software design.
So, what can we do to avoid this?
Composing Objects Principle
You can use the composing objects principle to gain a high amount of code reuse without using inheritance. This principle states that classes should achieve code reuse through aggregation rather than inheritance.
Design patterns, such as the composite design pattern and decorator design pattern use this design principle.
Composing objects does not force you to try and find commonalities between two classes, and couple them together like with inheritance. Instead, you’re able to design classes that can work together without having to share anything between them. This flexibility will also help you if the system requirements change.
To clarify, let’s look at a code snippet below, comparing both modes.
<?php
//Inheritance way
class Logger {
public function log(string $message): void {
echo "[LOG] " . $message . "<br>";
}
}
class UserService extends Logger {
public function createUser(string $name): void {
$this->log("Creating user: {$name}");
echo "User {$name} created.<br>";
}
}
<?php
//Composite way
class Logger {
public function log(string $message): void {
echo "[LOG] " . $message . "<br>";
}
}
class UserService {
private Logger $logger;
public function __construct(Logger $logger) {
$this->logger = $logger;
}
public function createUser(string $name): void {
$this->logger->log("Creating user: {$name}");
echo "User {$name} created.<br>";
}
}
Dynamic Behavior
In addition to less coupling, composition does allow dynamic behavior at runtime, because you can inject different objects into a class:
$userService = new UserService(new FileLogger());
// later…
$userService->setLogger(new DatabaseLogger());
What you can’t do with inheritance, once this relationship is fixed in the source code:
class UserService extends Logger {}Now, it’s not to say that inheritance should never be used. Composing objects in inheritance both have their place. Composing will give you better flexibility and less coupling while maintaining reusability, but that doesn’t mean it should always be used over inheritance.
You need to examine the needs of your system in order to determine which design principle is appropriate.
- Do you have a set of related classes or unrelated classes?
- What is a common behavior between them?
- Do you need specialized classes to handle specific cases or do you simply need a different implementation of the same behavior?
This are the different kinds of questions that you’ll need to ask yourself when designing your software systems.
Leave a Reply