LSP and Other SOLID Principles: Integration
LSP does not exist in isolation. It works in concert with the other SOLID principles to create maintainable object-oriented designs. Understanding these interactions is crucial for interview discussions.
LSP + Open-Closed Principle (OCP)
OCP states classes should be open for extension but closed for modification. LSP makes OCP practical by ensuring new subclasses work with existing client code without changes.
Connection: We can add WeekendDiscountFee without modifying ParkingLot because LSP guarantees all calculators behave consistently. The system is closed for modification (no changes to existing classes) but open for extension (new fee strategies).
LSP + Interface Segregation Principle (ISP)
ISP states clients should not be forced to depend on interfaces they do not use. LSP violations often indicate ISP violations: a subclass cannot implement all parent methods because the parent interface is too broad.
Worker interface with work() and eat(). RobotWorker cannot eat, violating LSP and forcing dependency on unused method (ISP).Workable and Eatable interfaces. HumanWorker implements both, RobotWorker implements only Workable.Solution approach:
- First, identify operations that not all subtypes can support.
- Second, extract those operations into separate interfaces.
- Third, let subclasses implement only the interfaces they can honor (LSP compliant).
- Fourth, client code depends only on the interfaces it needs (ISP compliant).
LSP + Single Responsibility Principle (SRP)
SRP states a class should have only one reason to change. LSP violations can indicate SRP violations when a parent class mixes multiple responsibilities, forcing some subclasses to implement irrelevant behavior.
class Document {
save() { /* file operations */ }
print() { /* printer operations */ }
render() { /* display operations */ }
}
// EmailDocument cannot print physically
// Violates LSP by throwing in print()
Refactored design: Separate persistence (Saveable), rendering (Renderable), and printing (Printable) concerns. Each document type implements only the interfaces matching its responsibilities.
LSP + Dependency Inversion Principle (DIP)
DIP states high-level modules should depend on abstractions, not concretions. LSP ensures those abstractions are robust: you can safely depend on the base type knowing all implementations are substitutable.
// Depends on abstraction (DIP)
constructor(feeCalculator: FeeCalculator) {
this.calculator = feeCalculator
}
calculateFee(duration) {
// Works for ANY FeeCalculator subtype (LSP)
return this.calculator.calculate(duration)
}
}
Key insight: DIP promotes depending on abstractions. LSP ensures those abstractions are dependable. Without LSP, you would need to check concrete types, defeating the purpose of DIP.
Practical Integration Example: Elevator System
+ getCapacity(): int
+ getCurrentFloor(): int
- SRP: Each interface has single responsibility (basic movement, express capability, freight capability).
- OCP: Can add new elevator types without modifying existing ones.
- LSP: All
Elevatorimplementations honor move/capacity/floor contracts. - ISP: Clients needing only basic elevator functions do not depend on express or freight interfaces.
- DIP: Controller depends on
Elevatorabstraction, not concrete types.
Common Interview Question
Strong answer structure:
FeeCalculator abstraction (DIP) with multiple strategies (OCP). Each strategy honors the calculation contract (LSP), and clients depend only on the calculator interface they need (ISP). The calculator itself has single responsibility (SRP): fee computation."