Skip to main content

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 A must still hold in B.
  • B must not weaken guarantees.
  • B must not violate assumptions of A.

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:

  1. There is a true is-a relationship.
  2. The subclass does not change invariants.
  3. The subclass only extends behavior, not contradict it.
  4. 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.

danger

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
  • Child inherits from Father and Mother.
  • Both parents have the same method name skills().
  • Python follows Method Resolution Order (MRO).
  • Since Father is 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
  • B and C inherit from A
  • D inherits 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
  • D inherits from B and C
  • 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​

FeatureMultiple InheritanceMulti-level InheritanceDiamond Problem
MeaningOne class inherits from multiple classesInheritance chainConflict in multiple inheritance
StructureA,B β†’ CA β†’ B β†’ CA β†’ B,C β†’ D
Main IssueMethod conflictNo conflictAmbiguity
SolutionMRONot neededMRO / 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
  • Parent inherits from Grandparent.

  • Child inherits from Parent.

  • Therefore Child can access:

    • house() from Grandparent
    • car() from Parent
    • bike() 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 logs was 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​

InheritanceComposition
"is-a""has-a"
Tight couplingLoose coupling
Can break invariantsPreserves invariants
Compile-time relationshipCan be dynamic
Harder to changeMore 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:

  • extend one class (abstract or not)
  • implement multiple 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...");
}
}
  • Bird extends Animal (a non-abstract class βœ…)
  • Bird implements Flyable (an interface βœ…)

Rules to remember

  1. Only one class can be extended

    class A {}
    class B {}
    class C extends A, B {} // ❌ Not allowed
  2. Multiple interfaces are allowed

    class C implements X, Y, Z {} // βœ…
  3. Must implement all interface methods

    • Unless the class is declared abstract

Simple way to think about it

  • extends β†’ "is-a" relationship (Bird is an Animal)
  • implements β†’ "can-do" abilities (Bird can Fly)