Design Pattern

Liskov Substitution Principle (LSP)

Clean Java-only production-ready implementation.


Subtypes must be substitutable for their base type without altering the correctness of the program.

A Vehicle base class assumes refuel(). ElectricScooter inherits it but cannot satisfy the contract.

Split capabilities into focused interfaces. ElectricScooter implements Rechargeable instead of forcing a no-op refuel().

// ─── EXAMPLE 1 ──────────────────────────────────────────────────────────────
// WHAT WE ARE IMPLEMENTING:
// An autonomous vehicle simulator ensuring all subclasses behave safely when
// substituted for parent interfaces.
//
// WHERE THE PRINCIPLE FITS IN:
// The code demonstrates that substituting ElectricCar for Car behaves
// correctly without breaking core interfaces, whereas violations like ToyCar
// throwing exceptions break expectations.
// ────────────────────────────────────────────────────────────────────────────
// --- Bad: LSP violation ---
abstract class Vehicle {
    abstract void start();
    abstract void refuel();  // Violation: not all vehicles refuel
}

class ElectricScooter extends Vehicle {
    void start() { System.out.println("  Scooter started silently"); }
    void refuel() { throw new UnsupportedOperationException("Scooters don't refuel"); }
    // ^ LSP violation: caller can't substitute Scooter where Vehicle is expected
}

// --- Good: LSP-safe design with capability interfaces ---
interface Startable { void start(); }
interface Refuelable { void refuel(); }
interface Rechargeable { void charge(); }

class PetrolCar implements Startable, Refuelable {
    public void start() { System.out.println("  [Car] Engine started"); }
    public void refuel() { System.out.println("  [Car] Tank filled"); }
}

class ElectricScooterFixed implements Startable, Rechargeable {
    public void start() { System.out.println("  [Scooter] Motor engaged silently"); }
    public void charge() { System.out.println("  [Scooter] Battery charging at 50kW"); }
}

// --- Client that works with any Startable ---
class GarageService {
    public void service(Startable vehicle) {
        System.out.println("  Starting service...");
        vehicle.start();
        // refuel/charge is not assumed — it's checked by capability
        if (vehicle instanceof Refuelable r) {
            r.refuel();
        } else if (vehicle instanceof Rechargeable c) {
            c.charge();
        }
        System.out.println("  Service complete");
    }
}

public class Main {
    public static void main(String[] args) {
        GarageService garage = new GarageService();

        Startable car = new PetrolCar();
        Startable scooter = new ElectricScooterFixed();

        garage.service(car);      // refuels
        garage.service(scooter);  // charges — no crash!
    }
}