27 Oct 2023



Intermediate

Examples of Violations of Liskov Substitution Principle (LSP) along with their corresponding solutions:

Example 1

Problem:

Consider a company that has a class hierarchy for its employees, such as Employee, Manager, and Engineer. The Employee class has a method called promote(). The Manager and Engineer classes override the promote() method to implement their own promotion behavior.

class Employee {
  public void promote() {
    System.out.println("Promoting employee...");
  }
}

class Manager extends Employee {
  @Override
  public void promote() {
    System.out.println("Promoting manager...");
  }
}

class Engineer extends Employee {
  @Override
  public void promote() {
    System.out.println("Promoting engineer...");
  }
}

// Client code
Employee employee = new Manager();
employee.promote(); // Prints "Promoting manager..."

employee = new Engineer();
employee.promote(); // Prints "Promoting engineer..."

This code snippet violates the LSP because a Manager object cannot be substituted for an Engineer object without breaking the application. The promote() method in the Engineer class may not be applicable to a manager.

Solution:

To address the violation, we can restructure the class hierarchy by introducing a more specific superclass, such as PromotableEmployee. The promote() method can then be placed in the relevant subclasses.

class Employee {
}

class PromotableEmployee extends Employee {
  public abstract void promote();
}

class Manager extends PromotableEmployee {
  @Override
  public void promote() {
    System.out.println("Promoting manager...");
  }
}

class Engineer extends PromotableEmployee {
  @Override
  public void promote() {
    System.out.println("Promoting engineer...");
  }
}

// Client code
PromotableEmployee employee = new Manager();
employee.promote(); // Promotes the manager.

employee = new Engineer();
employee.promote(); // Promotes the engineer.

This solution adheres to the LSP because Manager and Engineer objects can be substituted for PromotableEmployee objects without causing issues.


Example 2

Problem:

Consider a class hierarchy for different types of food, such as Food, Fruit, and Vegetable. The Food class has a method called eat(). The Fruit and Vegetable classes override the eat() method.

class Food {
  public void eat() {
    System.out.println("Eating food...");
  }
}

class Fruit extends Food {
  @Override
  public void eat() {
    System.out.println("Eating fruit...");
  }
}

class Vegetable extends Food {
  @Override
  public void eat() {
    System.out.println("Eating vegetable...");
  }
}

// Client code
Food food = new Fruit();
food.eat(); // Prints "Eating fruit..."

food = new Vegetable();
food.eat(); // Prints "Eating vegetable..."

This code snippet violates the LSP because a Vegetable object cannot be substituted for a Fruit object without potentially causing issues. Not all vegetables are suitable for eating raw.

Solution:

To rectify the issue, we can create a more specific superclass, such as EdibleFood, and move the eat() method there. This way, the subclasses, Fruit and Vegetable, can implement their specific eating behaviors.

class Food {
}

class EdibleFood extends Food {
  public abstract void eat();
}

class Fruit extends EdibleFood {
  @Override
  public void eat() {
    System.out.println("Eating fruit...");
  }
}

class Vegetable extends EdibleFood {
  @Override
  public void eat() {
    System.println("Eating vegetable...");
  }
}

// Client code
EdibleFood food = new Fruit();
food.eat(); // Prints "Eating fruit..."

food = new Vegetable();
food.eat(); // Prints "Eating vegetable..."

This solution adheres to the LSP because Fruit and Vegetable objects can be used interchangeably with EdibleFood objects without issues.


Example 3

Problem:

Consider a class hierarchy for animals, such as Animal, Bird, and Penguin. The Animal class has a method called makeSound(), which is overridden by Bird and Penguin.

class Animal {
  public void makeSound() {
    System.out.println("Making animal sound...");
  }
}

class Bird extends Animal {
  @Override
  public void makeSound() {
    System.out.println("Chirping...");
  }
}

class Penguin extends Bird {
  @Override
  public void makeSound() {
    System.out.println("Honking...");
  }
}

// Client code
Animal animal = new Bird();
animal.makeSound(); // Prints "Chirping..."

animal = new Penguin();
animal.makeSound(); // Prints "Honking..."

This code snippet violates the LSP because a Penguin object cannot be substituted for a Bird object without potentially causing issues. Penguins don't chirp; they make a different sound.

Solution:

To resolve the violation, we can introduce a more specific superclass, such as SoundMakingAnimal, and place the makeSound() method there. The subclasses, Bird and Penguin, can then implement their specific sound-making behaviors.

class Animal {
}

class SoundMakingAnimal extends Animal {
  public abstract void makeSound();
}

class Bird extends SoundMakingAnimal {
  @Override
  public void makeSound() {
    System.out.println("Chirping...");
  }
}

class Penguin extends SoundMakingAnimal {
  @Override
  public void makeSound() {
    System.out.println("Honking...");
  }
}

// Client code
SoundMakingAnimal animal = new Bird();
animal.makeSound(); // Prints "Chirping..."

animal = new Penguin();
animal.makeSound(); // Prints "Honking..."

This solution adheres to the LSP because Bird and Penguin objects can be used interchangeably with SoundMakingAnimal objects without causing issues.


Example 4

Problem:

Consider a class hierarchy for vehicles, including Vehicle, Car, and Airplane. The Vehicle class has a method called drive(), and both Car and Airplane override this method.

class Vehicle {
  public void drive() {
    System.out.println("Driving a vehicle...");
  }
}

class Car extends Vehicle {
  @Override
  public void drive() {
    System.out.println("Driving a car...");
  }
}

class Airplane extends Vehicle {
  @Override
  public void drive() {
    System.out.println("Flying a plane...");
  }
}

// Client code
Vehicle vehicle = new Car();
vehicle.drive(); // Prints "Driving a car..."

vehicle = new Airplane();
vehicle.drive(); // Prints "Flying a plane..."

This code snippet violates the LSP because an Airplane object cannot be used in place of a Vehicle object without potentially causing issues. The drive() method in the Vehicle class doesn't apply to an airplane.

Solution:

To rectify the violation, we can create a more specific superclass, such as LandVehicle, and move the drive() method there. Additionally, we introduce a new subclass, FlyingVehicle, to

implement the flying behavior for airplanes.

class Vehicle {
}

class LandVehicle extends Vehicle {
  public void drive() {
    System.out.println("Driving a land vehicle...");
  }
}

class Car extends LandVehicle {
}

class Airplane extends FlyingVehicle {
  public void fly() {
    System.out.println("Flying a plane...");
  }
}

class FlyingVehicle extends Vehicle {
  public void fly() {
    System.out.println("Flying a vehicle...");
  }
}

// Client code
Vehicle vehicle = new Car();
vehicle.drive(); // Prints "Driving a land vehicle..."

vehicle = new Airplane();
vehicle.fly(); // Prints "Flying a plane..."

This solution adheres to the LSP because an Airplane object cannot be used in place of a LandVehicle object. The drive() method is specific to LandVehicle.


Example 5

Problem:

Consider a class hierarchy for different types of vehicles, such as Vehicle, Car, and Truck. The Vehicle class has a method called load(), which is overridden by Car and Truck.

class Vehicle {
  public void load() {
    System.out.println("Loading vehicle...");
  }
}

class Car extends Vehicle {
  @Override
  public void load() {
    System.out.println("Loading car with passengers and luggage...");
  }
}

class Truck extends Vehicle {
  @Override
  public void load() {
    System.out.println("Loading truck with cargo...");
  }
}

// Client code
Vehicle vehicle = new Car();
vehicle.load(); // Prints "Loading car with passengers and luggage..."

vehicle = new Truck();
vehicle.load(); // Prints "Loading truck with cargo..."

This code snippet violates the LSP because a Truck object cannot be used in place of a Car object without potentially causing issues. The load() method in the Car class doesn't apply to a truck's cargo loading.

Solution:

To resolve the violation, we can introduce a more specific superclass, such as LoadableVehicle, and place the load() method there. The subclasses, Car and Truck, can then implement their specific loading behaviors.

class Vehicle {
}

class LoadableVehicle extends Vehicle {
  public abstract void load();
}

class Car extends LoadableVehicle {
  @Override
  public void load() {
    System.out.println("Loading car with passengers and luggage...");
  }
}

class Truck extends LoadableVehicle {
  @Override
  public void load() {
    System.out.println("Loading truck with cargo...");
  }
}

// Client code
LoadableVehicle vehicle = new Car();
vehicle.load(); // Loads the car with passengers and luggage.

vehicle = new Truck();
vehicle.load(); // Loads the truck with cargo.

This solution adheres to the LSP because Car and Truck objects can be used interchangeably with LoadableVehicle objects without causing issues.


Here is a short summary of some of the key points about the LSP:

  • The LSP is violated when a subclass cannot be used in place of its superclass without breaking the program.
  • There are a number of ways to fix LSP violations, such as refactoring the class hierarchy or using interfaces.
  • The LSP helps to ensure that code is more maintainable and reusable.
solid-principles
liskov-substitution-principle