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!
}
}