Knowledge Guide
HomeOO & Low-Level DesignSOLID Principles

Using Composition to Follow Liskov Substitution Principle LSP

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)

java
// Inheritance-based design that violates LSP
public class Bird {
    public void fly() {
        System.out.println("Bird is flying");
    }
}

public class Penguin extends Bird {
    @Override
    public void fly() {
        // Penguins can't fly, so this breaks the substitution
        throw new UnsupportedOperationException("Penguins can't fly");
    }
}

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

java
// Interface for flying behavior
public interface Flyable {
    void fly();
}

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.

java
// Bird class that contains general behavior for all birds
public class Bird {
    public void eat() {
        System.out.println("Bird is eating");
    }
}

// Class for flying birds that uses the Flyable interface
public class Sparrow extends Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Sparrow is flying");
    }
}

// Class for non-flying birds like Penguin
public class Penguin extends Bird {
    // Penguins do not implement Flyable, so no fly() method is needed
}

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

java
public class Main {
    public static void main(String[] args) {
        // Creating a Sparrow that can fly
        Sparrow mySparrow = new Sparrow();
        mySparrow.eat();
        mySparrow.fly();  // Works because sparrows can fly

        // Creating a Penguin that cannot fly
        Penguin myPenguin = new Penguin();
        myPenguin.eat();
        // myPenguin.fly();  // This will not compile, because Penguins don't have a fly() method
    }
}

How Composition Solves LSP

With composition:

java
// Flyable interface for birds that can fly
interface Flyable {
    void fly();
}

// Bird class containing general behavior for all birds
class Bird {
    public void eat() {
        System.out.println("Bird is eating");
    }
}

// Sparrow class that extends Bird and implements Flyable
class Sparrow extends Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Sparrow is flying");
    }
}

// Penguin class that extends Bird (no need for fly() method)
class Penguin extends Bird {
    // Penguins don't fly, so no fly method is included
}

// Main class to test the composition design
public class Solution {
    public static void main(String[] args) {
        // Creating a Sparrow that can fly
        Sparrow mySparrow = new Sparrow();
        mySparrow.eat();
        mySparrow.fly();  // Works fine

        // Creating a Penguin that cannot fly
        Penguin myPenguin = new Penguin();
        myPenguin.eat();  // Works fine
        // myPenguin.fly();  // This will not compile because Penguin doesn't have a fly() method
    }
}

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.

🤖 Don't fully get this? Learn it with Claude

Stuck on Using Composition to Follow Liskov Substitution Principle LSP? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Using Composition to Follow Liskov Substitution Principle LSP** (OO & Low-Level Design) and want to truly understand it. Explain Using Composition to Follow Liskov Substitution Principle LSP from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Using Composition to Follow Liskov Substitution Principle LSP** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Using Composition to Follow Liskov Substitution Principle LSP** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Using Composition to Follow Liskov Substitution Principle LSP** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes