Lesson explanation
After this lesson, you will be able to:
Inheritance says one class is a kind of another class (class PremiumAccount(Account)). Composition says an object has another object to help it do its work (Notifier has a Formatter). Both patterns are useful, but inheritance should represent a genuine type relationship rather than a shortcut for code reuse.
With composition, the outer object's constructor receives a collaborator and stores it. It then delegates work: self.formatter.render(message). Supplying the collaborator from the outside (constructor injection) makes it easier to switch behavior and test the outer class without editing it.
Do not automatically create a replacement collaborator in the constructor when the caller supplied one; that would ignore the chosen behavior. Composition also avoids large inheritance chains when the only goal is to combine separate responsibilities.
class Formatter:
def render(self, value):
return value.upper()
class Service:
def __init__(self, formatter):
self.formatter = formatter
def output(self, value):
return self.formatter.render(value)
service = Service(Formatter())
print(service.output("ready"))Output:
READYTwo formatters share the same method but produce different results.
class PlainFormatter:
def render(self, message):
return message
class BracketFormatter:
def render(self, message):
return f"[{message}]"
class Reporter:
def __init__(self, formatter):
self.formatter = formatter
def report(self, message):
return self.formatter.render(message)
print(Reporter(PlainFormatter()).report("ok"))
print(Reporter(BracketFormatter()).report("ok"))Output:
ok
[ok]Inheritance is appropriate when the child really is a specialized type of its parent.
class Storage:
def kind(self):
return "storage"
class CloudStorage(Storage):
def kind(self):
return "cloud storage"
print(CloudStorage().kind())Output:
cloud storageA cloud store is a kind of storage; a service has a formatter.
class RetryPolicy:
def can_retry(self, attempt):
return attempt < 3
class Worker:
def __init__(self, policy):
self.policy = policy
def should_retry(self, attempt):
return self.policy.can_retry(attempt)
print(Worker(RetryPolicy()).should_retry(2))Output:
True| Relationship | Example | Meaning |
|---|---|---|
| Inheritance (is-a) | class Admin(User) | Specialized type |
| Composition (has-a) | Report(formatter) | Uses another object |
| Dependency injection | self.formatter = formatter | Keep supplied collaborator |
| Delegation | self.formatter.render(text) | Ask collaborator to do its job |
self.formatter).