Composition vs Inheritance¶
Choose whether an object should be a specialized kind of something, or a collaboration of smaller reusable parts.
Once you understand classes, encapsulation, inheritance, and abstraction, the next design decision is architectural: should new behavior come from subclassing, or from plugging objects together?
Why This Matters¶
In business software, systems change constantly. Discount rules change. Notification channels change. Data sources change. Composition often makes these changes cheaper because you can swap one component without rebuilding the full class hierarchy.
Learning Goals¶
identify when inheritance models a true
is-arelationshipidentify when composition models a better
has-arelationshipcompare flexibility, reuse, and maintenance tradeoffs
prepare for the next notebook on interactions between collaborating classes
Decision Rule¶
Use inheritance when the child is genuinely a specialized version of the parent. Use composition when the object should be assembled from services, strategies, or helper objects.
| Question | Inheritance | Composition |
|---|---|---|
| Relationship | is-a | has-a / uses-a |
| Strength | simple reuse of shared behavior | high flexibility and swapping |
| Risk | rigid hierarchies, deep trees | more objects to coordinate |
| Example | SavingsAccount is an Account | CheckoutService has a PaymentStrategy |
Visual Intuition¶
Caption: Inheritance extends one parent type. Composition builds behavior by wiring together several collaborators.*
Worked Example: Flexible Checkout with Composition¶
No subclass explosion is needed here. We combine small parts to create the behavior we want.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def annual_cost(self):
return self.salary
class Manager(Employee):
def __init__(self, name, salary, bonus):
super().__init__(name, salary)
self.bonus = bonus
def annual_cost(self):
return self.salary + self.bonus
class BonusPolicy:
def bonus(self, salary):
return salary * 0.3
class PayrollProfile:
def __init__(self, salary, policy):
self.salary = salary
self.policy = policy
def total_compensation(self):
return self.salary + self.policy.bonus(self.salary)
manager = Manager('Karan', 120000, 15000)
executive_pay = PayrollProfile(12000, BonusPolicy())
print('Manager annual cost:', manager.annual_cost())
print('Executive compensation:', executive_pay.total_compensation())Guided Practice¶
Quick MCQ¶
A ReportService needs to support many export formats and new ones may be added every quarter. Which design is usually better?
A) Create one deep inheritance tree for every possible report combination
B) Compose the service with interchangeable exporter objects
C) Copy the export code into each report class
Correct answer: B
Reflection Questions¶
Why would composition be easier when notification channels change often?
Give one example where inheritance is still the clearer option.
What is a sign that your inheritance tree is becoming too rigid?
Answer Check
You can replace one notifier object without redesigning the whole class hierarchy.
SavingsAccountextendingAccountis often reasonable because the subtype still behaves like an account.You keep adding subclasses just to combine independent features such as payment, shipping, and notifications.
Exercises¶
Exercise 1¶
Build an OrderService that composes a TaxStrategy and a DeliveryStrategy.
Exercise 2¶
Model one case where inheritance is appropriate and justify the is-a relationship in one sentence.
Exercise 3¶
Take a class hierarchy you already know and ask whether some features should be extracted into helper objects instead of subclasses.
Starter Hint
Look for features that vary independently: pricing, exporting, notifications, recommendation logic, authentication, and storage are often better as composed collaborators.
Key Takeaway¶
Inheritance says a class becomes a specialized kind of parent. Composition says a class becomes useful by collaborating with other objects.
In modern business systems, composition often wins when behavior changes frequently. That sets up the next notebook on interactions between classes, where those collaborators begin exchanging messages in real workflows.