1. The Hook (The "Byte-Sized" Intro)
In a Nutshell: Inheritance is powerful but dangerous when misused. Three critical design principles govern good inheritance: Liskov Substitution Principle (LSP), Favor Composition Over Inheritance, and Program to an Interface. These principles prevent fragile, tightly-coupled code hierarchies that become unmaintainable nightmares.
Think of power tools. A chainsaw (inheritance) can build amazing things but can also cause damage if misused. Following safety principles (design principles) ensures you build, not destroy!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy: Building Construction
- LSP: A replacement part must work exactly like the original
- Composition > Inheritance: Use modular components instead of a monolithic structure
- Program to Interface: Design for pluggability, not specifics
Hand-Drawn Logic Map
3. Technical Mastery (The "Deep Dive")
1. Liskov Substitution Principle (LSP)
Definition: Objects of a superclass should be replaceable with objects of a subclass without breaking the application.
The "Why": If a Square extends Rectangle and overrides setWidth() to also change height (since squares have equal sides), code expecting Rectangle behavior breaks!
// ❌ Violates LSP
class Rectangle {
void setWidth(int w) { this.width = w; }
void setHeight(int h) { this.height = h; }
int area() { return width * height; }
}
class Square extends Rectangle {
@Override
void setWidth(int w) {
this.width = w;
this.height = w; // ❌ Breaks Rectangle's contract!
}
}
// Test breaks:
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(10);
assert r.area() == 50; // ❌ FAILS! (Returns 100)2. Favor Composition Over Inheritance
Use Inheritance When:
- True "is-a" relationship exists
- Polymorphism is needed
- Hierarchy is shallow (2-3 levels max)
Use Composition When:
- "Has-a" relationship
- Need flexibility to swap components
- Avoiding tight coupling
3. Program to an Interface
Definition: Depend on abstractions (interfaces/abstract classes), not concrete implementations.
// ✅ Good
List<String> list = new ArrayList<>(); // Depend on interface
// ❌ Bad
ArrayList<String> list = new ArrayList<>(); // Depend on concrete class4. Interactive & Applied Code
// Example: Payment System
// ❌ BAD: Inheritance-heavy
class Payment {
void process() { }
}
class CreditCardPayment extends Payment { }
class PayPalPayment extends Payment { }
// Problem: Can't combine behaviors (e.g., PayPal + Insurance)
// ✅ GOOD: Composition + Interface
interface PaymentMethod {
void process(double amount);
}
class PaymentProcessor {
private PaymentMethod method; // Composition!
PaymentProcessor(PaymentMethod method) {
this.method = method;
}
void processPayment(double amount) {
method.process(amount);
}
void changeMethod(PaymentMethod newMethod) {
this.method = newMethod; // Flexibility!
}
}
class CreditCard implements PaymentMethod {
public void process(double amount) {
System.out.println("Charging $" + amount + " to credit card");
}
}
class PayPal implements PaymentMethod {
public void process(double amount) {
System.out.println("Transferring $" + amount + " via PayPal");
}
}
public class Main {
public static void main(String[] args) {
PaymentProcessor processor = new PaymentProcessor(new CreditCard());
processor.processPayment(100);
// Easy to switch!
processor.changeMethod(new PayPal());
processor.processPayment(50);
}
}5. The Comparison & Decision Layer
Versus Table
| Scenario | Inheritance | Composition |
|---|---|---|
| Car HAS-AN Engine | ❌ Car extends Engine | ✅ Car contains Engine |
| Dog IS-AN Animal | ✅ Dog extends Animal | ❌ Dog contains Animal |
| Employee HAS-A Role | ❌ Employee extends Role | ✅ Employee contains Role |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question:
"Explain the Liskov Substitution Principle with an example."
Answer: LSP states that subclasses must be substitutable for their parent classes. Classic violation: Circle/Ellipse problem. If Circle extends Ellipse, setting different radii breaks Circle's contract. Solution: Use separate classes or composition.
Pro-Tip: The SOLID principles summary:
- Single Responsibility
- Open/Closed
- Liskov Substitution (we covered this!)
- Interface Segregation
- Dependency Inversion
These are the foundation of maintainable OOP!