CHAPTER 27 · INTERMEDIATE4 min read

Composition and Inheritance

Choose relationships that let objects collaborate without creating fragile hierarchies.

Python 3.12+Free to readModule 6 of 13
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Use composition to replace a collaborator.
  • Apply inheritance only when a subtype preserves the parent contract.
  • Explain overriding, super(), and behavioral compatibility.

01Compose a system from collaborators

Composition means one object uses another to perform part of its work. A report service can receive a formatter rather than inherit from a formatter. The service owns the reporting workflow, and the collaborator owns formatting. Supplying the collaborator through the constructor makes that dependency explicit and replaceable.

This design helps testing as well as reuse. A small in-memory collaborator can record calls without touching a network or file. Focus on the behavior the collaborator must provide, not its concrete class name. In Python, an object with the required method can often participate without inheriting a common base.

composition.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Inheritance promises substitutability

Inheritance creates a subtype relationship and reuses behavior through attribute lookup. A subclass can override a method, but the important question is whether callers using the parent contract still receive acceptable behavior. A read-only archive that inherits a writable store but rejects every save is a poor substitute for callers promised a writable store.

A valid override should not unexpectedly demand stricter inputs, return an incompatible kind of result, or remove essential effects. Shared code alone is a weak reason for inheritance. If two concepts merely need the same helper, a module function or composed helper may express the relationship more honestly.

inheritance.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03super participates in method resolution

Calling super().render delegates to the next implementation in the method resolution order. In simple single inheritance that is the parent implementation. It is more accurate to think "next implementation" than to assume super always means one fixed parent, because multiple inheritance can arrange a longer cooperative chain.

When a subclass defines __init__, it is responsible for arranging any required parent initialization, often through super().__init__(...). Otherwise parent attributes may never be set. Keep beginner hierarchies shallow; a deep chain makes it harder to know which override executes and which state every constructor must establish.

04Compare the cost of change

Choose composition when behavior varies independently: a storage backend, discount policy, renderer, or clock can be supplied as a collaborator. Choose inheritance when the domain has a stable subtype relationship and a useful common contract. Both techniques can coexist: a specialized report may inherit a report interface while composing a storage service.

Before adding a subclass, write a small caller that uses only the proposed parent interface. Can it handle both objects without special-case isinstance branches? If every caller must detect the subclass and compensate for surprises, the abstraction is not doing its job. Test shared contract behaviors against each implementation, including failure behavior, rather than testing only that inheritance syntax works.

LET IT CLICK

The ideas worth keeping.

  • Compose replaceable behaviors through small collaborator interfaces.
  • Use inheritance when subclasses preserve a meaningful contract.
  • Keep initialization and method-resolution behavior explicit.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Replace a delivery dependency

Build Reminder(sender) with remind(name). Inject a RecordingSender that collects messages; verify the reminder without performing real delivery.

your_turn_1.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
class RecordingSender:    def __init__(self):        self.messages = []    def send(self, message):        self.messages.append(message)class Reminder:    def __init__(self, sender):        self.sender = sender    def remind(self, name):        self.sender.send(f"{name}, review your notes")sender = RecordingSender()Reminder(sender).remind("Ada")assert sender.messages == ["Ada, review your notes"]

The workflow depends only on send(message). Composition makes the effect observable while keeping delivery outside the reminder logic.

02

Extend parent initialization safely

Create Member(name) and PremiumMember(name, credits). Preserve the parent label() contract and initialize both pieces of state.

your_turn_2.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
class Member:    def __init__(self, name):        self.name = name    def label(self):        return self.nameclass PremiumMember(Member):    def __init__(self, name, credits):        super().__init__(name)        self.credits = credits    def label(self):        return super().label() + " (premium)"member = PremiumMember("Lin", 5)assert member.label() == "Lin (premium)"assert member.credits == 5

The subclass establishes the parent state before adding its own. label still accepts the same arguments and returns a usable string.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01A report needs to swap between JSON and plain-text formatting independently of storage. Which design fits?
02What is the central promise of a subtype?
03What does super() select?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Replace a delivery dependency

Build Reminder(sender) with remind(name). Inject a RecordingSender that collects messages; verify the reminder without performing real delivery.

EXPECTED OUTPUT
[Process exited with code 0 - No output]
challenge.pyPython 3
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this chapter, when you want a second angle or the full details.

One chapter closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock chapter completion.