A Car is Not an Engine; It Contains an Engine
Consider an automobile. It contains an engine, a battery, wheels, hydraulic brakes, and a GPS unit. We would never say:
Instead, we recognize that Car has an Engine. Like snapping Lego bricks together, composing smaller specialized objects to construct a larger, cohesive entity is the very essence of modular engineering.
01 What is Composition?
In clear, definitive terms:
Rather than attempting to do everything itself, the composite object delegates specific sub-tasks to its internal components.
02 Inheritance vs. Composition: The Decisive Distinction
Distinguishing between these two paradigms is a milestone in your evolution as an architect:
“I am a specialized version of you.”
Constructs rigid, static taxonomy hierarchies (e.g., Car is a Vehicle). Produces tight coupling between base and derived classes.
“I use you to get work done.”
Holds references to independent objects (e.g., Car has an Engine). Highly flexible, swappable, and loosely coupled.
03 Composition and Delegation in Python
The core mechanism underpinning composition is Delegation: handing off the responsibility for a task to a dedicated, specialized helper object:
class Engine:
def start(self):
print("Engine ignition active: cylinders firing.")
class Battery:
def supply_power(self):
print("Battery supplying 12V direct current.")
class Car:
def __init__(self):
# Car encapsulates instances of Engine and Battery
self.engine = Engine()
self.battery = Battery()
def start(self):
print("Car ignition requested...")
# Delegating the actual mechanical work to internal components:
self.battery.supply_power()
self.engine.start()
print("Vehicle is fully operational and road-ready!")
# Usage
my_car = Car()
my_car.start()
Notice that Car.start() does not attempt to calculate cylinder timing; it cleanly delegates the ignition procedure to self.engine.start().
04 Why "Composition Over Inheritance" Conquered the Industry
Attempting to solve every software requirement with inheritance quickly leads to the dreaded Class Explosion anti-pattern:
One tweak to a root base class can inadvertently break dozens of child classes. Composition liberates systems from this trap with four decisive benefits:
Components remain independent and communicate strictly through public contracts, keeping implementation details private.
Swapping an internal combustion engine for an electric motor requires zero modifications to the car's inheritance tree—just plug in the new component.
In automated unit tests, you can easily inject simulated mock components instead of spinning up live external payment APIs or databases.
Rather than creating bloated "God Classes", responsibilities are partitioned cleanly into small, testable, and reusable units.
05
Real-World E-commerce Architecture: The Modular Order
Junior developers often fall into the trap of cramming taxes, payments, shipping, and discount algorithms directly into the Order class. Here is how composition restores clean architectural boundaries:
class OrderItem:
def __init__(self, name, price, qty):
self.name = name
self.price = price
self.qty = qty
@property
def subtotal(self):
return self.price * self.qty
class DiscountCalculator:
def apply(self, total):
return total * 0.9 # 10% promotional discount
class Order:
def __init__(self, customer, discount_service):
self.customer = customer
self.items = [] # Collection of OrderItem objects
self.discount_service = discount_service # Injected dependency
def add_item(self, item):
self.items.append(item)
def calculate_final_total(self):
raw_total = sum(item.subtotal for item in self.items)
# Delegating discount computation to the dedicated service
return self.discount_service.apply(raw_total)
# Usage
discount = DiscountCalculator()
order = Order("Salim", discount)
order.add_item(OrderItem("Python Mastery Book", 50, 2))
order.add_item(OrderItem("Engineering Notebook", 10, 1))
print(f"Final Total (with discount): ${order.calculate_final_total()}")
# Output: Final Total (with discount): $99.0
06 Dependencies & A Taste of Dependency Injection (DI)
Notice an essential subtlety in our order example: Order did not construct its own discount calculator internally. It was passed in through the constructor from the outside:
class OrderService:
def __init__(self):
# Hard-coded: impossible to swap or unit test cleanly!
self.payment = StripePayment()
class OrderService:
def __init__(self, payment_service):
# Injected from outside: easily accepts any gateway or mock!
self.payment = payment_service
Passing dependencies into objects rather than instantiating them internally is known as Dependency Injection, the cornerstone of modern scalable engineering.
07 The Hardware Analogy: Personal Computers
Recall Course 01 (Computer Foundation). What is a personal desktop computer made of?
A computer is not a CPU, nor is RAM a computer. A computer is an elegant Composition of distinct hardware modules. In software engineering, view your systems with the same architectural clarity: an enterprise application is a collaborating ensemble of Authentication, Storage, Database, and Notification services.
Concept Check
Verify your mastery of Lesson 08 concepts
Question 1: Which of the following relationships is best modeled using Composition?
Question 2: What is the primary motivation behind the "Composition over Inheritance" design principle?
Key Takeaways
is-a taxonomy exists; use Composition everywhere else.