Inheritance π
Inheritance allows one class (child/subclass) to reuse and extend another class (parent/superclass).
- It creates an "is-a" relationship: Dog is an Animal.
The Real Question: When Should You Use Inheritance?β
Use inheritance only when:
The subclass truly satisfies the behavioral contract and invariants of the superclass.
This is called the Liskov Substitution Principle (LSP):
If B extends A, then B must be usable anywhere A is expected without breaking correctness.
That means:
- All invariants of
Amust still hold inB. Bmust not weaken guarantees.Bmust not violate assumptions ofA.
Inheritance and Invariantsβ
This is where encapsulation becomes critical.
A superclass defines:
- Internal state
- Invariants
- Behavioral contract
If a subclass breaks those invariants, inheritance is wrong.
Classic Wrong Example: RectangleβSquare Problemβ
We define:
class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
Now someone writes:
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width;
}
@Override
public void setHeight(int height) {
this.width = height;
this.height = height;
}
}
Seems logical, right?
But Rectangleβs implicit invariant is:
Width and height are independent.
Squareβs invariant is:
Width == height.
Now this breaks substitutability:
Rectangle r = new Square();
r.setWidth(5);
r.setHeight(10);
System.out.println(r.getArea()); // Expected 50, gets 100
Boom.
Square cannot safely substitute Rectangle.
Inheritance violated invariants.
When Inheritance Is Correctβ
Use inheritance when:
- There is a true is-a relationship.
- The subclass does not change invariants.
- The subclass only extends behavior, not contradict it.
- You want polymorphic substitution.
Good Example: Shape Hierarchyβ
abstract class Shape {
public abstract double getArea();
}
Subclasses:
class Circle extends Shape {
private double radius;
public Circle(double radius) {
if (radius <= 0)
throw new IllegalArgumentException();
this.radius = radius;
}
public double getArea() {
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
private double width;
private double height;
public Rectangle(double width, double height) {
if (width <= 0 || height <= 0)
throw new IllegalArgumentException();
this.width = width;
this.height = height;
}
public double getArea() {
return width * height;
}
}
- No shared mutable invariant is broken.
- Both satisfy the contract: βa shape can compute area.β
- No subclass weakens guarantees.
This is safe inheritance.
Multiple Inheritanceβ
Multiple inheritance occurs when one class inherits from more than one parent class. So a child class can use the attributes and methods of multiple base classes.
Structure
Parent A Parent B
\ /
\ /
Child
Python, C++, Perl, Eiffel allow multiple inheritance, but java, c#, javascript, go, swift, kotlin doesn't allow.
Example (Python)
class Father:
def skills(self):
print("Gardening, Driving")
class Mother:
def skills(self):
print("Cooking, Painting")
class Child(Father, Mother):
def hobbies(self):
print("Playing games")
c = Child()
c.hobbies()
c.skills()
Output
Playing games
Gardening, Driving
Childinherits from Father and Mother.- Both parents have the same method name
skills(). - Python follows Method Resolution Order (MRO).
- Since
Fatheris written first (Child(Father, Mother)), Python calls Father's method first.
You can check the order:
print(Child.mro())
Output example:
[Child, Father, Mother, object]
Diamond Problemβ
The Diamond Problem occurs in multiple inheritance when:
- Two classes inherit from the same base class
- Another class inherits from those two classes
This creates a diamond-shaped inheritance structure.
Structure
A
/ \
B C
\ /
D
BandCinherit fromADinherits from both B and C
Problem
If class A has a method, and B and C override it, then D doesn't know which method to use.
Example (Python)
class A:
def show(self):
print("Class A")
class B(A):
def show(self):
print("Class B")
class C(A):
def show(self):
print("Class C")
class D(B, C):
pass
d = D()
d.show()
Output
Class B
Dinherits fromBandC- Both override
show() - Python resolves using MRO (Method Resolution Order).
Check order:
print(D.mro())
Example Output:
[D, B, C, A, object]
So Python searches in order:
D β B β C β A
Therefore it uses B.show().
abstract classes doesn't solve multiple inheritance.
How Python Solves the Diamond Problemβ
Python uses the C3 Linearization Algorithm automatically to determine method resolution order. C++ uses virtual inheritance so that only one shared copy exists, and the ambiguity is removed.
It ensures:
- No class is searched twice
- Order remains logical and predictable
Comparison Tableβ
| Feature | Multiple Inheritance | Multi-level Inheritance | Diamond Problem |
|---|---|---|---|
| Meaning | One class inherits from multiple classes | Inheritance chain | Conflict in multiple inheritance |
| Structure | A,B β C | A β B β C | A β B,C β D |
| Main Issue | Method conflict | No conflict | Ambiguity |
| Solution | MRO | Not needed | MRO / C3 linearization |
Multi-level Inheritanceβ
Multi-level inheritance occurs when a class inherits from a class which itself inherits from another class. It forms a chain of inheritance.
Structure
Grandparent
|
Parent
|
Child
Example (Python)
class Grandparent:
def house(self):
print("Grandparent's house")
class Parent(Grandparent):
def car(self):
print("Parent's car")
class Child(Parent):
def bike(self):
print("Child's bike")
c = Child()
c.house()
c.car()
c.bike()
Output
Grandparent's house
Parent's car
Child's bike
-
Parentinherits from Grandparent. -
Childinherits from Parent. -
Therefore
Childcan access:house()from Grandparentcar()from Parentbike()from itself.
This creates a 3-level inheritance hierarchy.
Compositionβ
Composition means:
Instead of inheriting behavior, you include another object as a field.
"Has-a" relationship instead of "is-a".
Example: Car and Engineβ
class Engine {
public void start() {
System.out.println("Engine started");
}
}
class Car {
private Engine engine;
public Car() {
this.engine = new Engine();
}
public void startCar() {
engine.start();
}
}
Car has an Engine.
Car is not an Engine.
This preserves encapsulation better.
Why Composition Is Often Betterβ
Inheritance:
- Exposes parent internals to child.
- Couples child tightly to parent.
- Can break invariants.
- Hard to modify superclass later.
Composition:
- Preserves encapsulation.
- Looser coupling.
- More flexible.
- Easier to change behavior dynamically.
Dangerous Inheritance Example (Fragile Base Class)β
Imagine:
class Logger {
protected List<String> logs = new ArrayList<>();
public void log(String message) {
logs.add(message);
}
}
Child class:
class TimestampLogger extends Logger {
@Override
public void log(String message) {
logs.add(System.currentTimeMillis() + ": " + message);
}
}
If Logger later changes internal storage:
- Subclass may break.
- Encapsulation was weak because
logswas protected.
Inheritance leaks implementation details.
Safer Alternative Using Compositionβ
class TimestampLogger {
private Logger logger;
public TimestampLogger(Logger logger) {
this.logger = logger;
}
public void log(String message) {
logger.log(System.currentTimeMillis() + ": " + message);
}
}
Now:
- Logger internals remain hidden.
- TimestampLogger depends only on public API.
- Encapsulation preserved.
Clear Decision Ruleβ
Use inheritance when:
- You want polymorphism
- There is a true "is-a" relationship
- Subclass does not change invariants
- You are extending behavior, not restricting it
Use composition when:
- Relationship is "has-a"
- You want flexibility
- You want to preserve encapsulation
- Behavior may change at runtime
- You are reusing functionality without true subtype meaning
Deep Insightβ
Inheritance is about type hierarchy.
Composition is about behavior reuse.
If your goal is:
- Code reuse β prefer composition.
- Substitutability β inheritance may be appropriate.
Golden Ruleβ
Favor composition over inheritance.
Because:
- Composition preserves encapsulation.
- Inheritance exposes it.
- Encapsulation protects invariants.
- Broken invariants break systems.
Final Comparisonβ
| Inheritance | Composition |
|---|---|
| "is-a" | "has-a" |
| Tight coupling | Loose coupling |
| Can break invariants | Preserves invariants |
| Compile-time relationship | Can be dynamic |
| Harder to change | More flexible |
Questionsβ
Is it possible to inherit child from a non-abstract class and interface?
A child class can both extend a concrete (non-abstract) class and implement one or more interfaces at the same time.
How it works
Java supports:
- Single inheritance (class β class)
- Multiple inheritance via interfaces
So a class can:
extendone class (abstract or not)implementmultiple interfaces
class Animal {
void eat() {
System.out.println("Eating...");
}
}
interface Flyable {
void fly();
}
class Bird extends Animal implements Flyable {
public void fly() {
System.out.println("Flying...");
}
}
BirdextendsAnimal(a non-abstract class β )BirdimplementsFlyable(an interface β )
Rules to remember
-
Only one class can be extended
class A {}
class B {}
class C extends A, B {} // β Not allowed -
Multiple interfaces are allowed
class C implements X, Y, Z {} // β -
Must implement all interface methods
- Unless the class is declared
abstract
- Unless the class is declared
Simple way to think about it
extendsβ "is-a" relationship (Bird is an Animal)implementsβ "can-do" abilities (Bird can Fly)