- Inheritance ā IS-A relationship, sharing code through parent classes
- Composition ā HAS-A relationship, building complex objects from simpler ones
- Key differences ā when to use each approach
- Pros and cons ā flexibility, reusability, and maintenance
- Real-world examples ā see both approaches in action
What's the Difference?
When you're designing classes in Python, you have two main ways to reuse code and build relationships between objects: inheritance and composition.
Here's the quick version:
- Inheritance is a "IS-A" relationship. A
DogIS-AAnimal. It inherits everything from the parent class. - Composition is a "HAS-A" relationship. A
CarHAS-AEngine. It contains other objects as parts.
The choice between inheritance and composition is one of the most important design decisions in object-oriented programming. Get it right, and your code will be flexible and easy to maintain. Get it wrong, and you'll end up with brittle, hard-to-change code.
š” Key concept: Inheritance is about being something. Composition is about having something. A dog is an animal. A car has an engine.
Inheritance ā IS-A Relationship
What is Inheritance?
Inheritance is when a class (child) derives from another class (parent). The child inherits all the methods and attributes of the parent and can add new ones or override existing ones.
The relationship is "IS-A". A Cat IS-A Animal. A Sedan IS-A Car.
Inheritance is great for sharing code and creating hierarchies of related classes. It's the foundation of polymorphism in many OOP languages.
# Inheritance ā IS-A Relationship
print("=" * 50)
print("INHERITANCE ā IS-A RELATIONSHIP")
print("=" * 50)
class Animal:
"""Base class ā all animals"""
def __init__(self, name):
self.name = name
self.is_alive = True
def breathe(self):
return f"{self.name} is breathing"
def sleep(self):
return f"{self.name} is sleeping"
def eat(self):
return f"{self.name} is eating"
def make_sound(self):
return f"{self.name} makes a sound"
class Dog(Animal):
"""Dog IS-A Animal ā inherits everything and adds/overrides"""
def __init__(self, name, breed):
super().__init__(name) # Call parent constructor
self.breed = breed
def make_sound(self):
"""Override: dogs bark"""
return f"{self.name} says: Woof! Woof!"
def wag_tail(self):
"""New method: specific to dogs"""
return f"{self.name} is wagging tail"
class Cat(Animal):
"""Cat IS-A Animal ā inherits everything and adds/overrides"""
def __init__(self, name, color):
super().__init__(name)
self.color = color
def make_sound(self):
"""Override: cats meow"""
return f"{self.name} says: Meow!"
def purr(self):
"""New method: specific to cats"""
return f"{self.name} is purring"
class Bird(Animal):
"""Bird IS-A Animal ā inherits everything and adds/overrides"""
def __init__(self, name, wingspan):
super().__init__(name)
self.wingspan = wingspan
def make_sound(self):
"""Override: birds chirp"""
return f"{self.name} says: Chirp! Chirp!"
def fly(self):
"""New method: specific to birds"""
return f"{self.name} is flying with wingspan {self.wingspan}cm"
# --- Demonstration ---
dog = Dog("Rex", "German Shepherd")
cat = Cat("Whiskers", "Orange")
bird = Bird("Tweety", 25)
print(" DOG (Dog IS-A Animal)")
print(f" {dog.breathe()}") # Inherited
print(f" {dog.sleep()}") # Inherited
print(f" {dog.eat()}") # Inherited
print(f" {dog.make_sound()}") # Overridden
print(f" {dog.wag_tail()}") # New method
print("\nš CAT (Cat IS-A Animal)")
print(f" {cat.breathe()}") # Inherited
print(f" {cat.make_sound()}") # Overridden
print(f" {cat.purr()}") # New method
print("\nš¦ BIRD (Bird IS-A Animal)")
print(f" {bird.breathe()}") # Inherited
print(f" {bird.make_sound()}") # Overridden
print(f" {bird.fly()}") # New method
print("\n" + "-" * 30)
print("INHERITANCE BENEFITS:")
print("-" * 30)
print(" Code reuse: common methods in Animal are shared")
print(" Polymorphism: each animal has its own make_sound()")
print(" Extensibility: new animals can be added easily")
print(" Clear hierarchy: relationships are explicit")
print("\n INHERITANCE CHALLENGES:")
print(" ⢠Can create deep, complex hierarchies")
print(" ⢠Changes in parent affect all children")
print(" ⢠Can lead to 'fragile base class' problems")
Inheritance key points:
- IS-A relationship ā a dog IS-A animal, a car IS-A vehicle
- Code reuse ā inherits all parent methods and attributes
- Override behavior ā child can change how methods work
- Create hierarchies ā organize classes in a tree structure
- Single inheritance ā Python classes can inherit from one parent (for main hierarchy)
Quick Check: What type of relationship does inheritance represent? (Answer: IS-A ā a dog IS-A animal)
Composition ā HAS-A Relationship
What is Composition?
Composition is when a class is built from other objects. Instead of inheriting behavior, a class contains instances of other classes and delegates work to them.
The relationship is "HAS-A". A Car HAS-A Engine. A Computer HAS-A Processor.
Composition is great for building complex objects from simpler parts and for decoupling code. It's more flexible than inheritance because you can change components at runtime.
# Composition ā HAS-A Relationship
print("=" * 50)
print("COMPOSITION ā HAS-A RELATIONSHIP")
print("=" * 50)
# ============================================================
# COMPONENT CLASSES ā The "parts"
# ============================================================
class Engine:
"""Engine component ā a car HAS-A engine"""
def __init__(self, horsepower, fuel_type):
self.horsepower = horsepower
self.fuel_type = fuel_type
self.is_running = False
def start(self):
self.is_running = True
return f"Engine started (HP: {self.horsepower}, Fuel: {self.fuel_type})"
def stop(self):
self.is_running = False
return "Engine stopped"
def get_power(self):
return f"Engine power: {self.horsepower} HP"
class Wheels:
"""Wheels component ā a car HAS-A wheels"""
def __init__(self, count=4):
self.count = count
def rotate(self):
return f"Rotating {self.count} wheels"
class Steering:
"""Steering component ā a car HAS-A steering"""
def __init__(self, type="power"):
self.type = type
def turn(self, direction):
return f"Turning {direction} with {self.type} steering"
class Transmission:
"""Transmission component ā a car HAS-A transmission"""
def __init__(self, type="automatic"):
self.type = type
self.gear = 0
def shift(self, gear):
self.gear = gear
return f"Shifted to gear {gear} ({self.type})"
class Radio:
"""Radio component ā a car HAS-A radio"""
def __init__(self):
self.is_on = False
self.station = "FM 101.1"
def turn_on(self):
self.is_on = True
return f"Radio on: {self.station}"
def turn_off(self):
self.is_on = False
return "Radio off"
# ============================================================
# COMPOSITE CLASS ā Built from components
# ============================================================
class Car:
"""Car built from components using composition"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
# Composition: Car HAS-A engine, wheels, steering, etc.
self.engine = Engine(200, "gasoline")
self.wheels = Wheels(4)
self.steering = Steering("power")
self.transmission = Transmission("automatic")
self.radio = Radio()
self.speed = 0
def start(self):
# Delegation: car uses its components
engine_status = self.engine.start()
return f"{self.make} {self.model}: {engine_status}"
def stop(self):
engine_status = self.engine.stop()
return f"{self.make} {self.model}: {engine_status}"
def drive(self, speed):
self.speed = speed
wheel_status = self.wheels.rotate()
trans_status = self.transmission.shift(3)
return f"Driving at {speed} km/h ā {wheel_status}, {trans_status}"
def turn(self, direction):
return self.steering.turn(direction)
def listen_radio(self):
return self.radio.turn_on()
def get_info(self):
return f"{self.year} {self.make} {self.model}"
# ============================================================
# DEMONSTRATION
# ============================================================
car = Car("Toyota", "Camry", 2023)
print("š CAR COMPOSITION")
print(f" {car.get_info()}")
print("\nš§ COMPONENTS:")
print(f" Engine: {car.engine.get_power()}")
print(f" Wheels: {car.wheels.count} wheels")
print(f" Steering: {car.steering.type} steering")
print(f" Transmission: {car.transmission.type}")
print("\n DRIVING THE CAR:")
print(f" {car.start()}")
print(f" {car.drive(60)}")
print(f" {car.turn('left')}")
print(f" {car.listen_radio()}")
print("\n" + "-" * 30)
print("COMPOSITION BENEFITS:")
print("-" * 30)
print("Flexible: can swap components (electric engine, different wheels)")
print(" Decoupled: components are independent")
print(" Reusable: same components can be used in different cars")
print(" Testable: components can be tested separately")
print(" Single responsibility: each component does one thing")
print("\n COMPOSITION CHALLENGES:")
print(" ⢠More code to write (need to set up components)")
print(" ⢠Delegation overhead (calls need to be passed through)")
print(" ⢠May need to expose component methods")
Composition key points:
- HAS-A relationship ā a car HAS-A engine, a computer HAS-A processor
- Build from parts ā complex objects are assembled from simpler ones
- Flexibility ā components can be swapped at runtime
- Decoupled ā components don't depend on each other
- Delegation ā the containing object forwards calls to its components
Quick Check: What type of relationship does composition represent? (Answer: HAS-A ā a car HAS-A engine)
Key Differences
Inheritance vs Composition ā Side by Side
# Inheritance vs Composition ā Complete Comparison
print("=" * 60)
print("INHERITANCE vs COMPOSITION")
print("=" * 60)
# ============================================================
# EXAMPLE 1: INHERITANCE APPROACH
# ============================================================
print("\n1. INHERITANCE APPROACH (IS-A)")
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def work(self):
return f"{self.name} is working"
def get_pay(self):
return f"Paid ${self.salary}"
class Manager(Employee):
def __init__(self, name, salary, team_size):
super().__init__(name, salary)
self.team_size = team_size
def work(self):
return f"{self.name} is managing {self.team_size} people"
def conduct_meeting(self):
return f"{self.name} is conducting a meeting"
class Developer(Employee):
def __init__(self, name, salary, language):
super().__init__(name, salary)
self.language = language
def work(self):
return f"{self.name} is coding in {self.language}"
def debug(self):
return f"{self.name} is debugging code"
print("=== INHERITANCE ===")
manager = Manager("Alice", 80000, 5)
dev = Developer("Bob", 60000, "Python")
print(f"Manager: {manager.work()}") # Overridden
print(f"Developer: {dev.work()}") # Overridden
print(f"Manager: {manager.get_pay()}") # Inherited
print(f"Manager: {manager.conduct_meeting()}") # New method
# ============================================================
# EXAMPLE 2: COMPOSITION APPROACH
# ============================================================
print("\n2. COMPOSITION APPROACH (HAS-A)")
class WorkBehavior:
"""Behavior component ā can be composed into employees"""
def __init__(self, role):
self.role = role
def do_work(self, name):
return f"{name} is working as a {self.role}"
class Payment:
"""Payment component ā can be composed into employees"""
def __init__(self, salary):
self.salary = salary
def pay(self, name):
return f"Paid {name} ${self.salary}"
class MeetingBehavior:
"""Meeting behavior ā can be composed into employees"""
def conduct_meeting(self, name):
return f"{name} is conducting a meeting"
class CodingBehavior:
"""Coding behavior ā can be composed into employees"""
def code(self, name, language):
return f"{name} is coding in {language}"
class EmployeeComposition:
"""Employee built from behaviors using composition"""
def __init__(self, name, role, salary):
self.name = name
self.work_behavior = WorkBehavior(role)
self.payment = Payment(salary)
def work(self):
return self.work_behavior.do_work(self.name)
def get_pay(self):
return self.payment.pay(self.name)
class ManagerComposition(EmployeeComposition):
"""Manager extends employee with additional behaviors"""
def __init__(self, name, salary):
super().__init__(name, "Manager", salary)
self.meeting_behavior = MeetingBehavior()
def conduct_meeting(self):
return self.meeting_behavior.conduct_meeting(self.name)
class DeveloperComposition(EmployeeComposition):
"""Developer extends employee with additional behaviors"""
def __init__(self, name, salary, language):
super().__init__(name, "Developer", salary)
self.coding_behavior = CodingBehavior()
self.language = language
def debug(self):
return f"{self.name} is debugging code"
def work(self):
# Can override behavior
return self.coding_behavior.code(self.name, self.language)
print("=== COMPOSITION ===")
manager2 = ManagerComposition("Alice", 80000)
dev2 = DeveloperComposition("Bob", 60000, "Python")
print(f"Manager: {manager2.work()}") # Delegated
print(f"Developer: {dev2.work()}") # Overridden/Delegated
print(f"Manager: {manager2.get_pay()}") # Delegated
print(f"Manager: {manager2.conduct_meeting()}") # Delegated
# ============================================================
# COMPARISON TABLE
# ============================================================
print("\n" + "=" * 60)
print("INHERITANCE vs COMPOSITION ā COMPARISON TABLE")
print("=" * 60)
print("""
āāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā INHERITANCE ā COMPOSITION ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā RELATIONSHIP ā IS-A (a dog IS-A animal) ā HAS-A (a car HAS-A engine) ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā CODE REUSE ā Inherits ALL parent methods ā Uses components as needed ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā FLEXIBILITY ā Fixed at compile time ā Can change at runtime ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā COUPLING ā Tightly coupled to parent ā Loosely coupled to components ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā CHANGES ā Parent changes affect child ā Component changes isolated ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā MULTIPLE REUSE ā Single inheritance ā Can use many components ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā BEST FOR ā Hierarchical relationships ā Building complex objects ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā TESTING ā Harder (parent must work) ā Easier (test components alone) ā
āāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā COMPLEXITY ā Simpler to understand ā More code, more structure ā
āāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
REMEMBER THE GOLDEN RULE:
⢠Inheritance is for "IS-A" relationships
⢠Composition is for "HAS-A" relationships
⢠Prefer composition over inheritance
⢠Inheritance is best for "is a special kind of"
""")
Comparison summary:
- Relationship ā inheritance = IS-A, composition = HAS-A
- Coupling ā inheritance creates tight coupling, composition creates loose coupling
- Flexibility ā composition is more flexible (can change components at runtime)
- Code reuse ā inheritance reuses everything, composition reuses only what you need
- Testing ā composition makes testing easier (components can be tested in isolation)
Quick Check: Which approach is more flexible: inheritance or composition? (Answer: Composition ā you can swap components at runtime)
When to Use What
Making the Right Choice
Choosing between inheritance and composition is about understanding the relationship between your classes. Here's a simple way to think about it:
# When to Use Inheritance vs Composition
print("=" * 60)
print("WHEN TO USE INHERITANCE vs COMPOSITION")
print("=" * 60)
# ============================================================
# USE INHERITANCE WHEN:
# ============================================================
print("\n USE INHERITANCE WHEN:")
print("\n1. You have a genuine IS-A relationship")
class Vehicle:
def move(self):
return "Moving"
class Car(Vehicle): # Car IS-A Vehicle
pass
print(" Car IS-A Vehicle ā good use of inheritance")
print("\n2. You want to share a common implementation")
class Logger:
def log(self, message):
print(f"LOG: {message}")
class FileLogger(Logger):
def log(self, message):
# Adds file writing but reuses the base behavior
with open("log.txt", "a") as f:
f.write(f"LOG: {message}\n")
super().log(message)
print(" FileLogger adds file logging AND reuses base log")
print("\n3. You need to override behavior")
class Animal:
def speak(self):
return "Some sound"
class Dog(Animal):
def speak(self):
return "Woof!"
print(" Dog overrides speak() for its own behavior")
# ============================================================
# USE COMPOSITION WHEN:
# ============================================================
print("\n\n USE COMPOSITION WHEN:")
print("\n1. You have a HAS-A relationship")
class Engine:
def start(self):
return "Engine started"
class Car:
def __init__(self):
self.engine = Engine() # Car HAS-A Engine
print(" Car HAS-A Engine ā good use of composition")
print("\n2. You want to change behavior at runtime")
class PaymentMethod:
def pay(self, amount):
return f"Paid ${amount}"
class Cash(PaymentMethod):
def pay(self, amount):
return f"Paid ${amount} in cash"
class CreditCard(PaymentMethod):
def pay(self, amount):
return f"Paid ${amount} with credit card"
class Order:
def __init__(self):
self.payment_method = PaymentMethod()
def set_payment_method(self, method):
self.payment_method = method
def pay(self, amount):
return self.payment_method.pay(amount)
order = Order()
order.set_payment_method(Cash())
print(f" Can switch payment method at runtime: {order.pay(100)}")
print("\n3. You want to use multiple behaviors from different sources")
class FlyBehavior:
def fly(self):
return "Flying"
class SwimBehavior:
def swim(self):
return "Swimming"
class Duck:
def __init__(self):
self.fly_behavior = FlyBehavior()
self.swim_behavior = SwimBehavior()
def fly(self):
return self.fly_behavior.fly()
def swim(self):
return self.swim_behavior.swim()
print(" Duck uses fly AND swim behaviors via composition")
# ============================================================
# DECISION GUIDE
# ============================================================
print("\n" + "=" * 60)
print("DECISION GUIDE")
print("=" * 60)
print("""
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ASK YOURSELF ā CHOOSE ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā "Is X a specialized kind of Y?" ā INHERITANCE (Dog IS-A Animal) ā
ā ā ā
ā "Does X have a Y?" ā COMPOSITION (Car HAS-A Engine) ā
ā ā ā
ā "Will the relationship change?" ā COMPOSITION (more flexible) ā
ā ā ā
ā "Do I need to share code?" ā Either (inheritance shares more) ā
ā ā ā
ā "Is this a deep hierarchy?" ā COMPOSITION (prevent deep inheritance) ā
ā ā ā
ā "Will this be used by others?" ā COMPOSITION (more decoupled) ā
ā ā ā
ā "Is this a simple, stable design?" ā INHERITANCE (simpler to implement) ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
GOLDEN RULE: "Prefer composition over inheritance"
Use inheritance only when it's the RIGHT tool for the job.
""")
Decision guide summary:
- Inheritance ā when you have a clear IS-A relationship and want to share code
- Composition ā when you have a HAS-A relationship and need flexibility
- Prefer composition ā it's more flexible and decoupled
- Use inheritance wisely ā only when it's the right tool
- Both can coexist ā many designs use both approaches
Quick Check: What's the golden rule for inheritance vs composition? (Answer: "Prefer composition over inheritance" ā use inheritance only when it's the right tool)
Real-World Example
Building an E-commerce System
# Real-World Example: E-commerce System
from datetime import datetime
import uuid
print("=" * 60)
print("E-COMMERCE SYSTEM ā INHERITANCE AND COMPOSITION")
print("=" * 60)
# ============================================================
# PART 1: INHERITANCE ā User Types
# ============================================================
class User:
"""Base user class ā uses inheritance for different user types"""
def __init__(self, username, email):
self.username = username
self.email = email
self.created_at = datetime.now()
def get_info(self):
return f"{self.username} ({self.email})"
def get_role(self):
return "user"
class Customer(User):
"""Customer IS-A User"""
def __init__(self, username, email):
super().__init__(username, email)
self.cart = []
self.order_history = []
def get_role(self):
return "customer"
def add_to_cart(self, product):
self.cart.append(product)
return f"Added {product} to cart"
def get_cart(self):
return f"{len(self.cart)} items in cart"
class Admin(User):
"""Admin IS-A User"""
def __init__(self, username, email):
super().__init__(username, email)
self.permissions = ["manage_users", "manage_products"]
def get_role(self):
return "admin"
def manage_users(self):
return "Managing users..."
class Vendor(User):
"""Vendor IS-A User"""
def __init__(self, username, email):
super().__init__(username, email)
self.products = []
self.sales = []
def get_role(self):
return "vendor"
def add_product(self, product):
self.products.append(product)
return f"Added product: {product}"
# ============================================================
# PART 2: COMPOSITION ā Order System
# ============================================================
class Product:
"""Product component ā used in orders and carts"""
def __init__(self, name, price, stock):
self.id = str(uuid.uuid4())[:8]
self.name = name
self.price = price
self.stock = stock
def __str__(self):
return f"{self.name} (${self.price})"
class Address:
"""Address component ā used in orders"""
def __init__(self, street, city, state, zip_code):
self.street = street
self.city = city
self.state = state
self.zip_code = zip_code
def __str__(self):
return f"{self.street}, {self.city}, {self.state} {self.zip_code}"
class OrderItem:
"""Order item component ā used in orders"""
def __init__(self, product, quantity):
self.product = product
self.quantity = quantity
self.subtotal = product.price * quantity
def __str__(self):
return f"{self.quantity}x {self.product.name} (${self.subtotal:.2f})"
class Payment:
"""Payment component ā used in orders"""
def __init__(self, method, amount):
self.method = method
self.amount = amount
self.status = "pending"
self.transaction_id = str(uuid.uuid4())[:8]
def process(self):
self.status = "completed"
return f"Payment of ${self.amount:.2f} via {self.method} processed"
def __str__(self):
return f"{self.method}: ${self.amount:.2f} ({self.status})"
class Order:
"""Order built from components using composition"""
def __init__(self, customer, address, payment_method):
# Composition: Order HAS-A customer, address, items, payment
self.customer = customer
self.address = address
self.order_date = datetime.now()
self.items = []
self.payment = Payment(payment_method, 0)
self.status = "pending"
def add_item(self, product, quantity=1):
if product.stock >= quantity:
item = OrderItem(product, quantity)
self.items.append(item)
product.stock -= quantity
return f"Added {quantity}x {product.name}"
return f"Not enough stock for {product.name}"
def calculate_total(self):
total = sum(item.subtotal for item in self.items)
self.payment.amount = total
return total
def process_order(self):
if not self.items:
return "Order has no items"
total = self.calculate_total()
payment_result = self.payment.process()
self.status = "processing"
return f"Order processed! Total: ${total:.2f} ā {payment_result}"
def get_summary(self):
return {
"order_id": str(uuid.uuid4())[:8],
"customer": self.customer.get_info(),
"address": str(self.address),
"items": [str(item) for item in self.items],
"total": f"${self.calculate_total():.2f}",
"payment": str(self.payment),
"status": self.status,
"date": self.order_date.strftime("%Y-%m-%d %H:%M")
}
# ============================================================
# DEMONSTRATION
# ============================================================
print("\n1. CREATING USERS (INHERITANCE)")
customer = Customer("alice", "alice@example.com")
admin = Admin("bob", "bob@example.com")
print(f" Customer: {customer.get_info()} (Role: {customer.get_role()})")
print(f" Admin: {admin.get_info()} (Role: {admin.get_role()})")
print("\n2. CREATING PRODUCTS")
product1 = Product("Laptop", 999.99, 5)
product2 = Product("Phone", 699.99, 10)
product3 = Product("Headphones", 149.99, 20)
print(f" {product1}")
print(f" {product2}")
print(f" {product3}")
print("\n3. PLACING AN ORDER (COMPOSITION)")
address = Address("123 Main St", "Boston", "MA", "02101")
order = Order(customer, address, "credit_card")
print(f" Order for: {customer.username}")
print(f" Address: {address}")
print(f" Adding items...")
print(f" {order.add_item(product1, 1)}")
print(f" {order.add_item(product2, 2)}")
print(f" {order.add_item(product3, 3)}")
print(f"\n4. ORDER SUMMARY")
summary = order.get_summary()
print(f" Order ID: {summary['order_id']}")
print(f" Customer: {summary['customer']}")
print(f" Total: {summary['total']}")
print(f" Status: {summary['status']}")
print(f"\n5. PROCESSING ORDER")
print(f" {order.process_order()}")
print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print(" INHERITANCE: Customer, Admin, Vendor all inherit from User")
print(" COMPOSITION: Order has Address, Payment, and OrderItem components")
print(" Both approaches work together in the same system")
print(" Choose inheritance for IS-A relationships (User types)")
print(" Choose composition for HAS-A relationships (Order components)")
Real-world example key points:
- Inheritance used for user types ā Customer, Admin, Vendor IS-A User
- Composition used for order components ā Order HAS-A Address, Payment, OrderItem
- Both approaches work together ā a complete system uses both
- Clear separation of concerns ā each component has a single responsibility
Quick Check: In the e-commerce example, what uses inheritance and what uses composition? (Answer: User types use inheritance; Order components use composition)
Best Practices
Using Inheritance and Composition Effectively
# Best Practices for Inheritance and Composition
print("=" * 60)
print("BEST PRACTICES")
print("=" * 60)
# ============================================================
# 1. USE INHERITANCE FOR GENUINE IS-A RELATIONSHIPS
# ============================================================
print("\n1. USE INHERITANCE FOR IS-A")
# DO: Use inheritance when there's a clear IS-A relationship
class Animal:
def breathe(self):
return "Breathing"
class Dog(Animal): # Dog IS-A Animal ā good!
def bark(self):
return "Woof!"
# DON'T: Use inheritance for HAS-A relationships
class Car(Engine): # Car IS-A Engine? No! Car HAS-A Engine
pass
# Instead, use composition:
class ProperCar:
def __init__(self):
self.engine = Engine() # Car HAS-A Engine ā good!
# ============================================================
# 2. USE COMPOSITION FOR FLEXIBILITY
# ============================================================
print("\n2. USE COMPOSITION FOR FLEXIBILITY")
class Renderer:
def render(self, data):
return f"Rendering: {data}"
class JSONRenderer(Renderer):
def render(self, data):
return f"JSON: {data}"
class XMLRenderer(Renderer):
def render(self, data):
return f"XML: {data}"
class DataProcessor:
def __init__(self, renderer):
self.renderer = renderer # Composition: processor HAS-A renderer
def process(self, data):
return self.renderer.render(data)
# Can change renderer at runtime
processor = DataProcessor(JSONRenderer())
print(f" JSON: {processor.process('data')}")
processor.renderer = XMLRenderer()
print(f" XML: {processor.process('data')}")
# ============================================================
# 3. AVOID DEEP INHERITANCE HIERARCHIES
# ============================================================
print("\n3. AVOID DEEP HIERARCHIES")
# DON'T: Create deep inheritance hierarchies
class A:
pass
class B(A):
pass
class C(B):
pass
class D(C):
pass
class E(D):
pass
# This is hard to maintain and understand
# DO: Use composition or keep hierarchies shallow
class Core:
pass
class Feature1:
pass
class Feature2:
pass
class MyClass:
def __init__(self):
self.core = Core()
self.feature1 = Feature1()
self.feature2 = Feature2()
# ============================================================
# 4. USE ABCs FOR INTERFACES
# ============================================================
print("\n4. USE ABCs FOR INTERFACES")
from abc import ABC, abstractmethod
class Drawable(ABC):
@abstractmethod
def draw(self):
pass
class Circle(Drawable):
def draw(self):
return "Drawing circle"
class Square(Drawable):
def draw(self):
return "Drawing square"
# ============================================================
# 5. DELEGATE RESPONSIBILITY
# ============================================================
print("\n5. DELEGATE RESPONSIBILITY")
class Logger:
def log(self, message):
return f"LOG: {message}"
class EmailSender:
def send(self, message):
return f"EMAIL: {message}"
class Service:
def __init__(self):
self.logger = Logger()
self.emailer = EmailSender()
def process(self, data):
# Delegate responsibilities
self.logger.log(f"Processing {data}")
self.emailer.send(f"Processed {data}")
return "Processed"
# ============================================================
# 6. SUMMARY
# ============================================================
print("\n" + "=" * 60)
print("BEST PRACTICES SUMMARY")
print("=" * 60)
print("""
āāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā PRACTICE ā WHY IT MATTERS ā
āāāāāāāāāāāāāāāāāāāāāāā¼āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā Use IS-A for ā Ensures inheritance is used appropriately ā
ā inheritance ā ā
ā ā ā
ā Use HAS-A for ā More flexible, easier to change ā
ā composition ā ā
ā ā ā
ā Keep hierarchies ā Easier to understand and maintain ā
ā shallow ā ā
ā ā ā
ā Use ABCs for ā Enforce contracts, better documentation ā
ā interfaces ā ā
ā ā ā
ā Delegate ā Keeps classes focused and maintainable ā
ā responsibility ā ā
ā ā ā
ā Prefer composition ā More flexible, less coupling, easier testing ā
ā over inheritance ā ā
āāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
REMEMBER:
⢠Inheritance: "IS-A" (Dog IS-A Animal)
⢠Composition: "HAS-A" (Car HAS-A Engine)
⢠"Favor composition over inheritance" is a design principle
⢠Both have their place ā use the right tool for the job
""")
Best practices summary:
- Use inheritance for IS-A ā only when it's a genuine relationship
- Use composition for HAS-A ā more flexible and decoupled
- Avoid deep hierarchies ā keep inheritance shallow
- Use ABCs for interfaces ā enforce contracts where needed
- Delegate responsibility ā keep classes focused
- Prefer composition ā it's more flexible and easier to test
Quick Check: What's the main reason to prefer composition over inheritance? (Answer: Composition is more flexible, less coupled, and easier to test)
Try It Yourself
Experiment with inheritance and composition in the editor below.
INHERITANCE vs COMPOSITION - PRACTICE
==================================================
1. INHERITANCE (IS-A)
Laptop: Lenovo ThinkPad, RAM: 16GB
Phone: Samsung Galaxy, Camera: 50MP
ThinkPad is ON
Taking photo with 50MP camera
2. COMPOSITION (HAS-A)
Smartphone: Apple iPhone 15
Apple iPhone 15: Processing Instagram on 8 cores at 2.5GHz
Displaying 'Video' on 6.1 inch screen ā Battery at 80%
Battery fully charged
3. COMPARISON
You've Got It!
You now understand the difference between inheritance and composition in Python. You know when to use each approach and how to design flexible, maintainable code.
Quick Quiz
Test what you've learned:
Frequently Asked Questions
What's the difference between inheritance and composition?
Is composition better than inheritance?
Can I use inheritance and composition together?
When should I use inheritance over composition?
What is the "fragile base class" problem?
How do I choose between inheritance and composition?
Where to Go From Here
Now that you understand the difference between inheritance and composition, check out these related topics:
Encapsulation
Learn how encapsulation works with inheritance and composition.
Learn More āPolymorphism
Learn how polymorphism works with both inheritance and composition.
Learn More āInheritance in Python
Deep dive into inheritance and its various types.
Learn More ā