Notes and exercises for learning design patterns
class ReportFacade:
def __init__(self, data_fetcher, formatter, emailer):
self._data_fetcher = data_fetcher
self._formatter = formatter
self._emailer = emailer
def send_weekly_report(self, recipients: list[str]) -> None:
data = self._data_fetcher.fetch_weekly_sales()
report = self._formatter.format(data)
self._emailer.send(report, recipients)
Three lines in the method body. That is all a basic Facade needs to do.
The caller goes from this:
fetcher = SalesDataFetcher()
formatter = ReportFormatter()
emailer = ReportEmailer()
data = fetcher.fetch_weekly_sales()
report = formatter.format(data)
emailer.send(report, recipients)
To this:
facade.send_weekly_report(recipients)
The caller no longer needs to know:
That is the core value of Facade: the orchestration lives in one place instead of leaking into every caller.
The constructor takes the subsystem objects rather than creating them:
def __init__(self, data_fetcher, formatter, emailer):
self._data_fetcher = data_fetcher
...
This is the Dependency Inversion Principle in action. Because the Facade receives its dependencies from outside, a test can pass in fakes:
class FakeEmailer:
def __init__(self):
self.sent = []
def send(self, report, recipients):
self.sent.extend(recipients)
emailer = FakeEmailer()
facade = ReportFacade(SalesDataFetcher(), ReportFormatter(), emailer)
facade.send_weekly_report(["alice@example.com"])
assert len(emailer.sent) == 1
If the Facade had created ReportEmailer() internally, the test would send
a real email. Dependency injection keeps the Facade testable.
A tempting mistake is to put orchestration logic in the caller:
# caller code — this is the smell
data = facade.fetch_data()
report = facade.format(report)
facade.email(report, recipients)
This is not a Facade — it is just wrapping individual subsystem calls behind a thin proxy. The caller still controls the ordering. The key signal that you have a real Facade is that the caller makes one call and gets the outcome, with no intermediate steps visible.