Notes and exercises for learning design patterns
Practice making a factory more extensible through registration.
In Exercise 3, the registry dictionary made the factory easier to scan:
_importers = {
".txt": PlainTextDocumentImporter,
".md": MarkdownDocumentImporter,
".html": HtmlDocumentImporter,
".json": JsonDocumentImporter,
}
But adding a new importer still required editing the factory’s registry.
In this exercise, you will let importer classes register themselves.
Open exercise4.py and complete DocumentImporterFactory so that this works:
@DocumentImporterFactory.register(".md")
class MarkdownDocumentImporter(DocumentImporter):
...
The factory should support:
.txt
.md
.html
.json
using decorator-based registration.
registerComplete this method:
@classmethod
def register(cls, extension: str):
...
It should return a decorator.
The decorator should:
cls._importersExpected usage:
@DocumentImporterFactory.register(".txt")
class PlainTextDocumentImporter(DocumentImporter):
...
create_for_fileComplete:
DocumentImporterFactory.create_for_file(path)
It should:
_importersValueError for unsupported file typesAdd support for .json by registering JsonDocumentImporter.
The factory class itself should not need a hardcoded .json branch.
The skeleton file includes temporary-file tests. After completing the exercise, this command should pass:
python exercise4.py
The tests check that decorator registration works:
md_path = root / "notes.md"
md_path.write_text("# Heading\n\nThis is **important**.", encoding="utf-8")
md_importer = DocumentImporterFactory.create_for_file(str(md_path))
assert isinstance(md_importer, MarkdownDocumentImporter)
assert "**" not in md_importer.import_document(str(md_path)).body
They also check the .json importer:
json_path = root / "notes.json"
json_path.write_text(
json.dumps({"title": "JSON Notes", "body": "This came from JSON."}),
encoding="utf-8",
)
json_importer = DocumentImporterFactory.create_for_file(str(json_path))
assert isinstance(json_importer, JsonDocumentImporter)
assert json_importer.import_document(str(json_path)).title == "JSON Notes"
The registry should contain the registered extensions:
assert ".txt" in DocumentImporterFactory._importers
assert ".md" in DocumentImporterFactory._importers
assert ".html" in DocumentImporterFactory._importers
assert ".json" in DocumentImporterFactory._importers
Unsupported files should raise ValueError:
try:
DocumentImporterFactory.create_for_file("notes.pdf")
except ValueError:
pass
else:
raise AssertionError("Unsupported file type should raise ValueError")
Decorator-based registration lets new classes attach themselves to the factory without editing the factory class.
This is more extensible than a hardcoded registry, but it adds indirection.
The key trade-off is:
More extensibility often means less obvious control flow.
Back to Factories and the Open/Closed Principle · Script · Solution