AiTechWorlds
AiTechWorlds
Imagine an engineer tasked with building a FlyingCar. The obvious approach: make it inherit from both Car and Plane. It IS-A Car. It IS-A Plane. Problem solved, right?
Not quite. A car has a steering wheel, four wheels, and drives on roads. A plane has wings, landing gear, and needs a runway. A FlyingCar inheriting from both gets everything — the landing gear conflicts with the car wheels, the wings interfere with the doors, and the engine systems are incompatible. You've created a Frankenstein class that's harder to maintain than either parent.
Now imagine a different approach. Instead of the FlyingCar being a car and a plane, it has a drive system and a flight system. You plug in the components you need. The components are independent, testable, and swappable. That is composition.
The core question in OOP design is always: does this relationship make logical sense?
Dog IS-A Animal. SavingsAccount IS-A BankAccount. The child is genuinely a specialized version of the parent.Car HAS-A Engine. User HAS-A EmailAddress. The class contains another object as one of its parts.If the sentence "X IS-A Y" sounds wrong or awkward, you probably want composition.
"A Logger IS-A Database" sounds wrong. "A Logger HAS-A DatabaseConnection" sounds right.
Inheritance creates tight coupling. When a base class changes, every subclass inherits that change — whether they want it or not.
class Animal:
def __init__(self, name: str):
self.name = name
def describe(self):
# Original developer adds a feature: log every description
print(f"[LOG] describe() called")
print(f"I am {self.name}")
class Dog(Animal):
def describe(self):
super().describe() # Calls Animal.describe()
print(f"I am also a dog!")
If someone changes Animal.describe() to add unexpected behavior, every single subclass is affected. With hundreds of subclasses, this becomes a maintenance nightmare. This is the fragile base class problem: the base class is fragile because changing it breaks subclasses in unpredictable ways.
Let's build a notification system. Users can receive emails, SMS messages, and push notifications.
class Notifier:
def send(self, message: str):
raise NotImplementedError
class EmailNotifier(Notifier):
def send(self, message: str):
print(f"Email: {message}")
class SMSNotifier(Notifier):
def send(self, message: str):
print(f"SMS: {message}")
# What if a user wants BOTH email AND SMS?
class EmailAndSMSNotifier(EmailNotifier, SMSNotifier):
# Multiple inheritance — which send() wins?
# Python uses MRO (Method Resolution Order) — confusing!
def send(self, message: str):
EmailNotifier.send(self, message) # Must call each manually
SMSNotifier.send(self, message)
# What about email + push? SMS + push? All three?
# You'd need: EmailAndPushNotifier, SMSAndPushNotifier,
# EmailSMSAndPushNotifier... combinatorial explosion!
The problem: every new combination requires a new class. With 4 notification channels, you could need up to 15 combination classes.
# Each notification channel is its own independent class
class EmailChannel:
def __init__(self, email_address: str):
self.email_address = email_address # Target email
def deliver(self, message: str):
print(f"Email to {self.email_address}: {message}")
class SMSChannel:
def __init__(self, phone_number: str):
self.phone_number = phone_number # Target phone
def deliver(self, message: str):
print(f"SMS to {self.phone_number}: {message}")
class PushChannel:
def __init__(self, device_token: str):
self.device_token = device_token # Device identifier
def deliver(self, message: str):
print(f"Push to device {self.device_token[:8]}...: {message}")
# The Notifier CONTAINS channel objects — it doesn't inherit from them
class Notifier:
def __init__(self):
self.channels = [] # List to hold any number of channel objects
def add_channel(self, channel):
self.channels.append(channel) # Plug in any channel at runtime
return self # Enable method chaining
def notify(self, message: str):
for channel in self.channels: # Deliver via every registered channel
channel.deliver(message)
Using the composed system:
# User wants email + SMS
user_notifier = Notifier()
user_notifier.add_channel(EmailChannel("alice@example.com"))
user_notifier.add_channel(SMSChannel("+1-555-0100"))
user_notifier.notify("Your order has shipped!")
Output:
Email to alice@example.com: Your order has shipped!
SMS to +1-555-0100: Your order has shipped!
Adding push notifications requires zero class changes — just add_channel(PushChannel(...)).
Sometimes you want to add behavior to a class without full inheritance. Mixins are small, focused classes designed to be mixed into other classes.
class JSONMixin:
"""Adds JSON serialization to any class."""
def to_json(self):
import json
return json.dumps(self.__dict__, default=str) # Serialize object attributes
class TimestampMixin:
"""Adds created_at and updated_at tracking."""
from datetime import datetime
def touch(self):
self.updated_at = str(__import__('datetime').datetime.now()) # Set timestamp
class User(JSONMixin, TimestampMixin):
def __init__(self, name: str, email: str):
self.name = name # User's display name
self.email = email # User's email address
user = User("Alice", "alice@example.com")
print(user.to_json())
# Output: {"name": "Alice", "email": "alice@example.com"}
Mixins work well when the behavior is truly orthogonal — it applies to many unrelated classes without modification.
| Situation | Use | Reason |
|---|---|---|
| Child is a genuine specialization of parent | Inheritance | IS-A relationship is clear and stable |
| Child needs behavior from multiple unrelated classes | Composition | Avoids multiple inheritance confusion |
| Base class changes frequently | Composition | Isolates changes, prevents fragile base class |
| You need to swap parts at runtime | Composition | Objects can be replaced without subclassing |
| Adding small, reusable behaviors | Mixin | Clean way to share orthogonal functionality |
| Hierarchy is more than 2-3 levels deep | Composition | Deep hierarchies are hard to reason about |
Building a FlyingCar by inheriting from Car and Plane creates chaos. Building one that has a drive system and a flight system creates a vehicle you can actually maintain.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises