0% completed
In the previous lesson, we explored how using inheritance can sometimes violate the Liskov Substitution Principle. Another way to follow LSP without falling into the pitfalls of inheritance is by using composition over inheritance. This method allows you to design systems that are more flexible and avoid situations where subclasses cannot properly fulfill the parent class's contract.
Example: Bird and Flying Behavior
Let’s use the example of birds. If we use inheritance, we might create a Bird
class that defines general bird behaviors, like flying. However, some birds—like penguins—cannot fly.
Inheritance forces subclasses to implement or override behaviors they don’t need, which can lead to LSP violations.
Problem with Inheritance (Violating LSP)
Here, the Penguin
class is forced to inherit the fly()
method from the Bird
class, even though penguins can’t fly. This violates LSP because substituting a Penguin
for a Bird
causes the program to break when fly()
is called.
Solution: Using Composition to Follow LSP
To avoid this issue, we can use composition. Instead of forcing birds to inherit flying behavior, we can create a separate Flyable
interface and only assign flying abilities to birds that can actually fly.
Step 1: Create Interfaces for Flyable and Non-Flyable Birds
Step 2: Implement Bird Classes with Composition
We now separate the flying ability from the bird itself. Birds that can fly will implement the Flyable
interface, while birds that can’t fly won’t inherit unnecessary behavior.
In this solution, we use composition by creating a Flyable
interface. Now, birds that can fly, like sparrows, implement the Flyable
interface, while penguins do not. This keeps the hierarchy clean and avoids forcing subclasses to override or implement unnecessary behavior.
Step 3: Test the Composition-Based Design
How Composition Solves LSP
With composition:
- No Unnecessary Methods: Birds that can’t fly (like penguins) aren’t forced to implement or override the
fly()
method, avoiding LSP violations. - Flexible Design: You can easily add other behaviors to birds (such as swimming) without affecting birds that don’t need that behavior.
- Follows LSP: You can substitute
Sparrow
forBird
without worrying about breaking the program, asfly()
is only available to birds that can actually fly.
Using composition over inheritance allows us to avoid violating the Liskov Substitution Principle. By separating specific behaviors like flying into an interface, we avoid forcing all bird subclasses to implement or override methods that don’t apply to them. This creates a more flexible, maintainable design that follows LSP and allows each class to behave according to its own characteristics.
.....
.....
.....
On this page
Example: Bird and Flying Behavior
Problem with Inheritance (Violating LSP)
Solution: Using Composition to Follow LSP
Step 1: Create Interfaces for Flyable and Non-Flyable Birds
Step 2: Implement Bird Classes with Composition
Step 3: Test the Composition-Based Design
How Composition Solves LSP