Notes and exercises for learning design patterns
Practice two things that arise naturally as a Facade grows:
The reporting tool from Exercise 1 has grown. The company now needs two kinds of reports: the existing weekly sales report and a new monthly executive summary.
The monthly summary has a different flow:
data_fetcher.fetch_monthly_sales()).kpi_fetcher.fetch_kpis()).formatter.format_executive(data, kpis)).archiver.save(report, filename)).emailer.send(report, recipients)).There is also a new error requirement: if the archiver fails (raises
ArchiveError), the email must not be sent. The caller should receive a
clean ReportDeliveryError instead of a raw ArchiveError. The caller
should never have to handle subsystem-specific exceptions.
Extend ReportFacade with a second method:
facade.send_monthly_executive_summary(
recipients=["board@example.com"],
archive_filename="executive_2024_10.txt",
)
And wrap errors so that:
try:
facade.send_monthly_executive_summary(
recipients=["board@example.com"],
archive_filename="executive_2024_10.txt",
)
except ReportDeliveryError as e:
print(f"Delivery failed: {e}")
Never leaks ArchiveError to the caller.
The exercise file adds:
KpiFetcher — has fetch_kpis() -> dictReportArchiver — has save(report: str, filename: str) -> None; may raise ArchiveErrorArchiveError — exception raised by the archiver on failureReportDeliveryError — the exception your Facade should raise to callersThe existing SalesDataFetcher, ReportFormatter, and ReportEmailer are
extended with the new methods needed.
ReportFacade.__init__ now also accepts kpi_fetcher and archiver.ArchiveError propagate out of the Facade.ReportDeliveryError (with a descriptive message) when archiving fails.exercise2.py
Run the tests with:
python -m pytest exercise2.py -v