Can anyone provide an example of the Liskov Substitution Principle (LSP) using Vehicles?

For me, this 1996 Quote from Uncle Bob (Robert C Martin) summarises the LSP best:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

In recent times, as an alternative to inheritance abstractions based on sub-classing from a (usually abstract) base/super class, we also often use interfaces for polymorphic abstraction. The LSP has implications to both the consumer, and implementation of the abstraction:

  • Any code consuming a class or interface abstraction must assume nothing else about the class beyond the defined abstraction;
  • Any subclassing of a superclass or implementation of an abstraction must adhere to the requirements and conventions of the interface to the abstraction.

Uncle Bob, and others regard the LSP as a cornerstone of Design by Contract.

Example, LSP Compliance

Here is an example using an interface IVehicle which can have multiple implementations (alternatively, you can substitute the interface for an abstract base class with several subclasses – same effect).

interface IVehicle
   void Drive(int miles);
   void FillUpWithFuel();
   int FuelRemaining {get; } // C# syntax for a readable property

This implementation of a consumer of IVehicle stays within the bounds of LSP:

void MethodWhichUsesIVehicle(IVehicle aVehicle)
   // Knows only about the interface. Any IVehicle is supported

Glaring Violation – Runtime type switching

Here’s an example of a violation of LSP, using RTTI and then Downcasting – Uncle Bob calls this a ‘glaring violation’:

void MethodWhichViolatesLSP(IVehicle aVehicle)
   if (aVehicle is Car)
      var car = aVehicle as Car;
      // Do something special for car - this method is not on the IVehicle interface
    // etc.

The violating method goes beyond the contracted IVehicle interface and hacks a specific path for a known implementation of the interface (or a subclass, if using inheritance instead of interfaces). Uncle Bob also explains that LSP violations using type-switching behaviour usually also violate the Open and Closed principle as well, since continual modification to the function will be required in order to accomodate new subclasses.

Violation – Pre condition is strengthened by a subtype

Another violation example would be where a “pre condition is strengthened by a subtype”:

public abstract class Vehicle
    public virtual void Drive(int miles)
        Assert(miles > 0 && miles < 300); // Consumers see this as the contract

 public class Scooter : Vehicle
     public override void Drive(int miles)
         Assert(miles > 0 && miles < 50); // ** Violation

Here, the Scooter subclass attempts to Violate the LSP as it tries to strengthen (further constrain) the precondition on the base class Drive method that miles < 300, to now a maximum of less than 50 miles. This is invalid, since by the contract definition of Vehicle allows 300 miles.

Similarly, Post Conditions may not be weakened (i.e. relaxed) by a subtype.

(Users of Code Contracts in C# will note that preconditions and postconditions MUST be placed on the interface via a ContractClassFor class, and cannot be placed within implementation classes, thus avoiding the violation)

Subtle Violation – Abuse of an interface implementation by a subclass

A more subtle violation (also Uncle Bob’s terminology) can be shown with a dubious derived class which implements the interface:

class ToyCar : IVehicle
    public void Drive(int miles) { /* Show flashy lights, make random sounds */ }
    public void FillUpWithFuel() {/* Again, more silly lights and noises*/}
    public int FuelRemaining {get {return 0;}}

Here, irrespective of how far the ToyCar is driven, the fuel remaining will always be zero, which will be surprising to users of the IVehicle interface (i.e. infinite MPG consumption – perpetual motion?). In this case, the problem is that despite ToyCar having implemented all of the requirements of the interface, ToyCar just inherently isn’t a real IVehicle and just “rubber stamps” the interface.

One way to to prevent your interfaces or abstract base classes from being abused in this way is to ensure a good set of Unit Tests are made available on the interface / abstract base class to test that all implementations meet the expectations (and any assumptions). Unit tests are also great at documenting typical usage. e.g. this NUnit Theory will reject ToyCar from making it into your production code base:

void EnsureThatIVehicleConsumesFuelWhenDriven(IVehicle vehicle)
    Assert.IsTrue(vehicle.FuelRemaining > 0);
    int fuelBeforeDrive = vehicle.FuelRemaining;
    vehicle.Drive(20); // Fuel consumption is expected.
    Assert.IsTrue(vehicle.FuelRemaining < fuelBeforeDrive);

Edit, Re: OpenDoor

Opening doors sounds like a different concern entirely, so needs to be separated accordingly (i.e. the “S” and “I” in SOLID), e.g.

Add a separate interface IDoor, and then vehicles like Car and Truck would implement both IVehicle and IDoor interfaces, but Scooter and Motorcycle would only implement IVehicle.

In all cases, to avoid violating LSP, code which required objects of these interfaces should not downcast the interface to access extra functionality. The code should select the appropriate minimum interface / (super)class it needs, and stick to just the contracted functionality on that interface.

Leave a Comment