关于里氏替换原则与接口隔离原则的示例困惑咨询
Let's break this down with a concrete example since you mentioned confusion around Motorcycle's implementation—this is a super common pain point with LSP and ISP, so let's walk through it step by step.
1. Liskov Substitution Principle (LSP)
First, remember LSP's core rule: Subtypes must be substitutable for their base types without breaking the program's correctness. No exceptions, no unexpected behavior—just seamless replacement.
Let’s start with a naive (and problematic) setup:
// Base Vehicle class public class Vehicle { protected int doorCount; public int getDoorCount() { return doorCount; } public void startEngine() { // Common engine startup logic } } // Car subclass (works fine) public class Car extends Vehicle implements IVehicle { public Car() { this.doorCount = 4; } } // Motorcycle subclass (the problem child) public class Motorcycle extends Vehicle implements IVehicle { public Motorcycle() { this.doorCount = 0; // Or worse: throw an UnsupportedOperationException in getDoorCount() } }
Why this breaks LSP
Imagine a function elsewhere in your code that expects a Vehicle and uses getDoorCount() to lock all doors:
public void lockAllDoors(Vehicle vehicle) { for (int i = 0; i < vehicle.getDoorCount(); i++) { // Execute door-locking logic } }
Passing a Car here works exactly as expected. But passing a Motorcycle? Either it does nothing (if doorCount is 0) or crashes (if you threw an exception). This violates LSP because the Motorcycle can’t be substituted for Vehicle without breaking the function’s intended behavior.
Fix for LSP
Split the base hierarchy to match actual behavioral contracts. Create a specialized subclass for vehicles with doors, so Motorcycle only inherits what it actually needs:
// Base Vehicle with only universal features public class Vehicle { public void startEngine() { // Common engine logic } } // Specialized subclass for door-equipped vehicles public class DoorEquippedVehicle extends Vehicle { protected int doorCount; public int getDoorCount() { return doorCount; } } // Car uses the door-equipped subclass public class Car extends DoorEquippedVehicle implements IVehicle { public Car() { this.doorCount = 4; } } // Motorcycle inherits only the base Vehicle public class Motorcycle extends Vehicle implements IVehicle { // No door-related methods at all }
Now lockAllDoors can accept DoorEquippedVehicle instead of Vehicle, and Motorcycle never enters that function—no more broken behavior.
2. Interface Segregation Principle (ISP)
ISP’s core rule: Clients should not be forced to depend on interfaces they do not use. In short, don’t make classes implement methods they’ll never actually use.
Let’s look at a problematic overloaded interface:
// Bloated interface with methods for all vehicle types public interface IVehicle { void startEngine(); void refuel(); void openDoor(); void deploySideStand(); }
Here, Car has to implement deploySideStand() (even though it has no stand) and Motorcycle has to implement openDoor() (even though it has no doors). This forces both classes to carry unused, meaningless methods—violating ISP.
Why this breaks ISP
Clients shouldn’t be exposed to methods they don’t need. For example, a FuelStation class only needs to start engines and refuel vehicles—why should it have access to openDoor() or deploySideStand()? It creates unnecessary dependencies and clutter.
Fix for ISP
Split the large interface into smaller, focused ones that match specific roles:
// Interface for engine-related operations public interface IEngineOperable { void startEngine(); void refuel(); } // Interface for door-equipped vehicles public interface IDoorEquipped { void openDoor(); } // Interface for vehicles with a side stand public interface ISupportStand { void deploySideStand(); }
Now each class implements only the interfaces relevant to its functionality:
public class Car extends DoorEquippedVehicle implements IEngineOperable, IDoorEquipped { @Override public void startEngine() { /* ... */ } @Override public void refuel() { /* ... */ } @Override public void openDoor() { /* ... */ } } public class Motorcycle extends Vehicle implements IEngineOperable, ISupportStand { @Override public void startEngine() { /* ... */ } @Override public void refuel() { /* ... */ } @Override public void deploySideStand() { /* ... */ } }
Clients can now depend on exactly what they need: a FuelStation uses IEngineOperable, a CarService uses IEngineOperable + IDoorEquipped, and a MotorcycleShop uses IEngineOperable + ISupportStand. No more unused code!
Quick Recap
- LSP is about behavioral consistency: If a subtype can’t fully fulfill the base type’s contract, split the hierarchy to avoid forcing incompatible behavior.
- ISP is about interface granularity: Keep interfaces focused so classes only implement methods they actually use.
内容的提问来源于stack exchange,提问作者user1959214

