Understanding the Liskov Substitution Principle with Real-World Examples

Hello, fellow software designers! Today, we’re diving into the Liskov Substitution Principle (LSP), one of the five SOLID principles in software design. This principle ensures that our software components are reliable, flexible, and easy to maintain. To make this concept clearer, we’ll explore a real-world analogy and then translate it into a practical software example.

What is the Liskov Substitution Principle?

The Liskov Substitution Principle (LSP) states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In simpler terms, if a program works correctly with a parent class, it should also work with any subclass derived from that parent class.

Real-World Analogy: Vehicle Rental Service

Imagine a vehicle rental service that offers different types of vehicles for rent, such as cars, bicycles, and electric scooters. The rental service has a general policy: “All vehicles should be drivable.” Customers should be able to rent any type of vehicle and drive it away without any unexpected issues.

Violation of LSP:

  1. Car: Works fine. It has a steering wheel, an engine, and brakes. Customers can drive it as expected.
  2. Bicycle: Also works fine. It has handlebars, pedals, and brakes. Customers can ride it as expected.
  3. Electric Scooter: However, let’s say the scooter has a design flaw: when the customer tries to accelerate, it requires charging for 2 hours before use!

In this scenario, the Electric Scooter would violate the expectation set by the rental service that all vehicles should be drivable immediately. It’s not substitutable for other vehicles because it introduces unexpected behavior that doesn’t conform to the policy.

Correct Example: Following the LSP

To adhere to the Liskov Substitution Principle, all vehicles should be immediately drivable when rented:

  1. Car: Comes with a full tank of gas or is fully charged if it’s electric.
  2. Bicycle: Is well-maintained and has properly inflated tires.
  3. Electric Scooter: Is always charged and checked before being rented out.

By ensuring all vehicles meet the same “drivable” criteria, we maintain substitutability without causing confusion or inconvenience to the customer.

Translating LSP into Code

Now, let’s translate this analogy into a practical code example.

Implementing the Liskov Substitution Principle in Ruby

We will define a base class Vehicle with a method drive, and then create subclasses for different types of vehicles: Car, Bicycle, and ElectricScooter. Each subclass will provide its own implementation of the drive method, ensuring they all conform to the expectations of being immediately drivable.

class Vehicle
  # The base method that all subclasses must implement
  def drive
    raise NotImplementedError, "Subclasses must implement the drive method"
  end
end

class Car < Vehicle
  def drive
    "Driving the car... ready to go!"
  end
end

class Bicycle < Vehicle
  def drive
    "Riding the bicycle... ready to go!"
  end
end

class ElectricScooter < Vehicle
  def drive
    "Riding the electric scooter... ready to go!"
  end
end

Testing Substitutability
To demonstrate that each subclass can be substituted for the base class Vehicle, we create a method that takes a Vehicle and calls the drive method.

def test_drive(vehicle)
  puts vehicle.drive
end

# Testing with different vehicle types
car = Car.new
bicycle = Bicycle.new
scooter = ElectricScooter.new

test_drive(car)        # Output: "Driving the car... ready to go!"
test_drive(bicycle)    # Output: "Riding the bicycle... ready to go!"
test_drive(scooter)    # Output: "Riding the electric scooter... ready to go!"

Why This Example Follows the LSP

  1. Consistent Behavior: All vehicle subclasses (Car, Bicycle, ElectricScooter) implement the drive method, ensuring consistent behavior when substituting any one for the base class Vehicle.
  2. No Unexpected Errors: There are no surprises or errors when using any subclass where a Vehicle is expected. Each subclass fulfills the contract set by the Vehicle base class.

Conclusion

The Liskov Substitution Principle ensures that subclasses can replace their parent class without breaking the program. By adhering to this principle, we create more robust, maintainable, and flexible software systems.

Remember, whether you’re designing a vehicle rental service or a complex software system, ensuring substitutability is key to building reliable and user-friendly applications.

Stay tuned for more insights into software design principles and patterns!

Thôi Lo Code Đi Kẻo Sếp nạt!!