Inheritance and composition
Substitute compatible objects and assemble behavior
Reusing behavior
Inheritance lets a subclass reuse and override base-class behavior. Composition gives an object collaborators and delegates work to them.
Use inheritance when the subclass preserves the base contract: accepted inputs, results, exceptions, and state expectations. Use composition for replaceable policies such as formatting or storage.
class UpperFormatter:
def format(self, text):
return text.upper()
class Display:
def __init__(self, formatter):
self.formatter = formatter
def show(self, text):
return "[" + self.formatter.format(text) + "]"
print(Display(UpperFormatter()).show("hello")) # [HELLO]Display accepts a formatter through behavior rather than ancestry. Another compatible formatter can replace it without changing the caller. A function may suffice for a single stateless operation. If a collaborator owns resources, define who releases them.
Method resolution and super()
super() follows the instance's method resolution order (MRO), which can include sibling classes in a multiple-inheritance hierarchy:
class Base:
def trace(self):
return ["base"]
class Left(Base):
def trace(self):
return ["left"] + super().trace()
class Right(Base):
def trace(self):
return ["right"] + super().trace()
class Combined(Left, Right):
pass
print(Combined().trace()) # ['left', 'right', 'base']For this instance, super() inside Left.trace reaches Right.trace. Calling Base.trace directly would skip that step. Cooperative implementations need compatible signatures, argument forwarding, and an agreed terminal method.
Gotchas
- Code reuse alone does not establish a subtype. Stronger preconditions or changed return meanings can break existing callers.
- Matching method names do not guarantee matching semantics. State mutation and resource ownership belong in the interface contract.
- Exact-class branches in callers make new implementations harder to add; test through the common interface instead.
- Shared class-level mutable state can couple subclasses. Keep hierarchies small enough that their assumptions remain visible.
Put it into practice
Implement the exercise in a full-screen workspace, then run its tests.