Notes and exercises for learning design patterns
Practice refactoring a simple factory from if / elif logic into a registry dictionary.
This exercise also shows an important design trade-off:
A registry can make the factory easier to scan, but it does not completely solve the Open/Closed Principle problem.
In Exercise 2, the factory probably looked like this:
if suffix == ".txt":
return PlainTextDocumentImporter()
if suffix == ".md":
return MarkdownDocumentImporter()
if suffix == ".html":
return HtmlDocumentImporter()
This is fine for a few cases.
But as the number of supported file types grows, a dictionary can make the mapping clearer.
Open exercise3.py and complete the registry-based factory.
You need to implement:
DocumentImporterFactory._importers
DocumentImporterFactory.create_for_file(path)
JsonDocumentImporter.import_document(path)
The factory should support:
.txt -> PlainTextDocumentImporter
.md -> MarkdownDocumentImporter
.html -> HtmlDocumentImporter
.json -> JsonDocumentImporter
Create a class-level dictionary like this:
_importers = {
".txt": PlainTextDocumentImporter,
".md": MarkdownDocumentImporter,
".html": HtmlDocumentImporter,
".json": JsonDocumentImporter,
}
The values should be classes, not instances.
That means the factory can instantiate the chosen class:
return importer_class()
JsonDocumentImporter should read a JSON file shaped like this:
{
"title": "Notes",
"body": "This came from JSON."
}
It should return a Document.
Unsupported file types should raise ValueError.
The skeleton file includes temporary-file tests. After completing the exercise, this command should pass:
python exercise3.py
The tests check JSON support:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
json_path = root / "notes.json"
json_path.write_text(
json.dumps({"title": "JSON Notes", "body": "This came from JSON."}),
encoding="utf-8",
)
importer = DocumentImporterFactory.create_for_file(str(json_path))
assert isinstance(importer, JsonDocumentImporter)
document = importer.import_document(str(json_path))
assert document.title == "JSON Notes"
assert document.body == "This came from JSON."
The registry should still support existing formats:
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
Unsupported files should raise ValueError:
try:
DocumentImporterFactory.create_for_file("notes.pdf")
except ValueError:
pass
else:
raise AssertionError("Unsupported file type should raise ValueError")
if / elif chain?A registry dictionary improves organization:
_importers = {
".txt": PlainTextDocumentImporter,
".md": MarkdownDocumentImporter,
".html": HtmlDocumentImporter,
".json": JsonDocumentImporter,
}
But adding a new importer still requires modifying the registry.
So this is cleaner than a long conditional, but not a complete Open/Closed Principle solution.
Back to Factories and the Open/Closed Principle · Script · Solution