Inheritance & Reusability
How Inheritance Promotes Reusability
Inheritance establishes a “IS-A” relationship between classes, allowing a subclass to reuse and extend the attributes and methods of a superclass.
By inheriting common functionality, you avoid duplicating code and can override or extend behaviour where necessary.
Deep Dive: Inheritance
Purpose of Inheritance
Inheritance allows a subclass to automatically acquire the attributes and methods of a superclass, promoting code reuse and logical organisation. Instead of rewriting common functionality, you define it once in the parent class and extend or customise it in child classes.
How It Works
When Class B inherits from Class A:
- Class B gains all public and protected fields and methods declared in Class A.
- Class B can add new fields or methods, and override inherited methods to change behaviour.
- Objects of Class B can be treated as instances of Class A (polymorphism), enabling flexible code.
Access Modifiers and Inheritance
- public members are inherited and accessible everywhere.
- protected members are inherited and accessible in subclasses (and within the same package in Java).
- private members are not inherited: subclasses cannot access them directly.
- default (package-private in Java) members are inherited only by classes in the same package.
Hierarchy: Vehicle → Car → Regular, Hybrid, Electric
This example shows a Car subclass extending a Vehicle superclass to reuse fuel behaviour. It goes on to show how RegularCar, HybridCar, and ElectricCar then extends Car. Here, we have a hierarchy of Vehicle → Car → Regular, Hybrid, Electric.
The code samples below show how this hierarchy can be implemented.
class Vehicle:
def __init__(self, fuel: float):
self._fuel = float(fuel) # protected by convention
def start(self):
print("Vehicle started, fuel:", f"{self._fuel:.2f}")
def drive(self, km: float):
self._fuel -= km * 0.1
print(f"Drove {km} km; remaining fuel: {self._fuel:.2f}")
class Car(Vehicle):
def __init__(self, make: str, model: str, fuel: float):
super().__init__(fuel)
self.make = make
self.model = model
def honk(self):
print(f"{self.make} {self.model} goes 'beep!'")
class RegularCar(Car):
# A standard gasoline/diesel car using base fuel logic.
def start(self):
print(f"RegularCar started: {self.make} {self.model}, fuel: {self._fuel:.2f}")
def refuel(self, liters: float):
self._fuel += liters
print(f"Refueled {liters} L; fuel now: {self._fuel:.2f}")
class ElectricCar(Car):
# Interpret _fuel as battery charge units (e.g. kWh or %)
def __init__(self, make: str, model: str, battery: float):
super().__init__(make, model, battery)
def start(self):
print(f"ElectricCar ready: {self.make} {self.model}, battery: {self._fuel:.2f}")
def drive(self, km: float):
# EVs consume differently; simple model using 0.15 per km
consumption_rate = 0.15
self._fuel -= km * consumption_rate
print(f"Drove {km} km on battery; remaining charge: {self._fuel:.2f}")
def recharge(self, amount: float):
self._fuel += amount
print(f"Recharged {amount}; battery now: {self._fuel:.2f}")
class HybridCar(Car):
# Hybrid with both a battery and a fuel tank. _fuel is the liquid fuel; _battery is electric
def __init__(self, make: str, model: str, fuel: float, battery: float):
super().__init__(make, model, fuel)
self._battery = float(battery)
def start(self):
print(f"HybridCar started: {self.make} {self.model}, fuel: {self._fuel:.2f}, battery: {self._battery:.2f}")
def drive(self, km: float):
# Battery-first strategy: use battery at 0.05/km, then fuel at 0.08/km.
battery_rate = 0.05
fuel_rate = 0.08
max_batt_km = min(km, self._battery / battery_rate if battery_rate > 0 else 0.0)
self._battery -= max_batt_km * battery_rate
remaining_km = km - max_batt_km
self._fuel -= remaining_km * fuel_rate
print(
f"Drove {km} km (battery {max_batt_km:.2f} km, fuel {remaining_km:.2f} km); "
f"remaining -> fuel: {self._fuel:.2f}, battery: {self._battery:.2f}"
)
def refuel(self, liters: float):
self._fuel += liters
print(f"Refueled {liters} L; fuel now: {self._fuel:.2f}")
def recharge(self, amount: float):
self._battery += amount
print(f"Recharged {amount}; battery now: {self._battery:.2f}")
# --- Simple test (main) ---
if __name__ == "__main__":
print("== RegularCar ==")
car = RegularCar("Toyota", "Corolla", 50.0)
car.start()
car.drive(20)
car.honk()
car.refuel(10)
print("\n== ElectricCar ==")
ev = ElectricCar("Tesla", "Model 3", 75.0)
ev.start()
ev.drive(50)
ev.honk()
ev.recharge(5)
print("\n== HybridCar ==")
hv = HybridCar("Toyota", "Prius", 20.0, 10.0)
hv.start()
hv.drive(100)
hv.honk()
hv.recharge(3)
hv.refuel(4)
class Vehicle {
protected double fuel;
public Vehicle(double fuel) {
this.fuel = fuel;
}
public void start() {
System.out.println("Vehicle started, fuel: " + String.format("%.2f", fuel));
}
public void drive(double km) {
this.fuel -= km * 0.1;
System.out.println("Drove " + km + " km; remaining fuel: " + String.format("%.2f", fuel));
}
}
class Car extends Vehicle {
protected String make;
protected String model;
public Car(String make, String model, double fuel) {
super(fuel);
this.make = make;
this.model = model;
}
public void honk() {
System.out.println(make + " " + model + " goes 'beep!'");
}
}
class RegularCar extends Car {
public RegularCar(String make, String model, double fuel) {
super(make, model, fuel);
}
@Override
public void start() {
System.out.println("RegularCar started: " + make + " " + model + ", fuel: " + String.format("%.2f", fuel));
}
public void refuel(double liters) {
this.fuel += liters;
System.out.println("Refueled " + liters + " L; fuel now: " + String.format("%.2f", fuel));
}
}
class ElectricCar extends Car {
// Interpret 'fuel' as battery charge units for simplicity.
public ElectricCar(String make, String model, double battery) {
super(make, model, battery);
}
@Override
public void start() {
System.out.println("ElectricCar ready: " + make + " " + model + ", battery: " + String.format("%.2f", fuel));
}
@Override
public void drive(double km) {
double consumptionRate = 0.15; // per km
this.fuel -= km * consumptionRate;
System.out.println("Drove " + km + " km on battery; remaining charge: " + String.format("%.2f", fuel));
}
public void recharge(double amount) {
this.fuel += amount;
System.out.println("Recharged " + amount + "; battery now: " + String.format("%.2f", fuel));
}
}
class HybridCar extends Car {
private double battery;
public HybridCar(String make, String model, double fuel, double battery) {
super(make, model, fuel);
this.battery = battery;
}
@Override
public void start() {
System.out.println(
"HybridCar started: " + make + " " + model +
", fuel: " + String.format("%.2f", fuel) +
", battery: " + String.format("%.2f", battery)
);
}
@Override
public void drive(double km) {
double batteryRate = 0.05; // per km
double fuelRate = 0.08; // per km
double maxBattKm = Math.min(km, (batteryRate > 0 ? battery / batteryRate : 0.0));
battery -= maxBattKm * batteryRate;
double remainingKm = km - maxBattKm;
fuel -= remainingKm * fuelRate;
System.out.println(
"Drove " + km + " km (battery " + String.format("%.2f", maxBattKm) +
" km, fuel " + String.format("%.2f", remainingKm) + " km); remaining -> fuel: " +
String.format("%.2f", fuel) + ", battery: " + String.format("%.2f", battery)
);
}
public void refuel(double liters) {
this.fuel += liters;
System.out.println("Refueled " + liters + " L; fuel now: " + String.format("%.2f", fuel));
}
public void recharge(double amount) {
this.battery += amount;
System.out.println("Recharged " + amount + "; battery now: " + String.format("%.2f", battery));
}
}
public class Main {
public static void main(String[] args) {
System.out.println("== RegularCar ==");
RegularCar car = new RegularCar("Toyota", "Corolla", 50.0);
car.start();
car.drive(20);
car.honk();
car.refuel(10);
System.out.println("\n== ElectricCar ==");
ElectricCar ev = new ElectricCar("Tesla", "Model 3", 75.0);
ev.start();
ev.drive(50);
ev.honk();
ev.recharge(5);
System.out.println("\n== HybridCar ==");
HybridCar hv = new HybridCar("Toyota", "Prius", 20.0, 10.0);
hv.start();
hv.drive(100);
hv.honk();
hv.recharge(3);
hv.refuel(4);
}
}
Example: Animal → Dog, Cat
Both Dog and Cat inherit from an abstract Animal type and implement make_sound(). As with all classes, an abstract class is designed to define attributes and methods. However, an object is never instantiated from an abstract class. In this case, it would be hard to imagine creating an object of Animal: what would it be?
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def make_sound(self) -> None:
pass
class Dog(Animal):
def make_sound(self) -> None:
print("Woof!")
class Cat(Animal):
def make_sound(self) -> None:
print("Meow!")
# Usage
for pet in (Dog(), Cat()):
pet.make_sound()
abstract class Animal {
public abstract void makeSound();
}
class Dog extends Animal {
@Override public void makeSound() { System.out.println("Woof!"); }
}
class Cat extends Animal {
@Override public void makeSound() { System.out.println("Meow!"); }
}
public class ZooDemo {
public static void main(String[] args) {
Animal[] pets = { new Dog(), new Cat() };
for (Animal pet : pets) pet.makeSound();
}
}
Other Inheritance Examples
Employee & Manager
Suppose you have a Employee class with attributes name, id, and
salary, plus a method calculatePay(). A Manager subclass can
inherit these, then add a department field and override calculatePay()
to include bonus calculations:
- Inherited:
name,id,salary,calculatePay() - Added:
department,calculateBonus() - Overridden:
calculatePay()to call superclass logic then add bonus
Vehicle & ElectricVehicle
This example describes a slightly different version of the Vehicle example above.
A Vehicle class might define make, model and methods
startEngine(), drive(). An ElectricVehicle subclass
inherits those then adds an attribute batteryLevel and method
chargeBattery(). It can also override drive() to consume battery instead of fuel.
- Inherited:
make,model,startEngine(),drive() - Added:
batteryLevel,chargeBattery() - Overridden:
drive()to reducebatteryLevel
Key Takeaways
- Inheritance creates a hierarchy that promotes code reuse.
- Subclasses inherit
publicandprotectedmembers, but notprivate. - Use inheritance judiciously—deep hierarchies can be hard to maintain.
- Access modifiers control what is inherited and who can invoke it.