Card 08/ 09

ExerciseDifficulty: Advanced2 min

Practice: An Editor That Opens Text and Spreadsheet Documents

The editor's open() is written once; each kind of editor creates its own document.

An editor that opens several kinds of document is the Gang of Four's own example of Factory Method, and this exercise asks you to design it. Applying the pattern yourself means deciding which classes play each participant and where the factory method goes, so do that before writing any code.

An editor has an open(path) method that creates a document, loads it from the path, adds it to the list of open documents and reports how many are open. Every editor does those steps the same way. A text editor creates text documents and a spreadsheet editor creates spreadsheets, and more kinds of editor will follow.

  1. Name the class that plays each participant: creator, concrete creator, product and concrete product.
  2. Name the factory method and say which class declares it.
  3. Write open so that it never names TextDocument or SpreadsheetDocument.
Show a solution
The editor as Factory Method

Editor is the creator and TextEditor and SpreadsheetEditor are the concrete creators. Document is the product, and TextDocument and SpreadsheetDocument are the concrete products. The factory method is create_document, declared abstract on Editor.

py
from abc import ABC, abstractmethod


class Document(ABC):
    @abstractmethod
    def load(self, path: str) -> str: ...


class TextDocument(Document):
    def load(self, path: str) -> str:
        return f"text document from {path}"


class SpreadsheetDocument(Document):
    def load(self, path: str) -> str:
        return f"spreadsheet from {path}"


class Editor(ABC):
    def __init__(self) -> None:
        self.open_documents: list[Document] = []

    @abstractmethod
    def create_document(self) -> Document: ...

    def open(self, path: str) -> None:
        document = self.create_document()
        self.open_documents.append(document)
        print(f"opened {document.load(path)}, {len(self.open_documents)} open")


class TextEditor(Editor):
    def create_document(self) -> Document:
        return TextDocument()


class SpreadsheetEditor(Editor):
    def create_document(self) -> Document:
        return SpreadsheetDocument()


TextEditor().open("notes.txt")
SpreadsheetEditor().open("budget.csv")
OutputRan with python:3.12-slim (Python 3.12.13)
opened text document from notes.txt, 1 open
opened spreadsheet from budget.csv, 1 open
`open` calls `create_document` and works with a `Document` only.

A presentation editor would add PresentationEditor and PresentationDocument, and open would not change. A common wrong answer puts the if for the document kind back inside open, which is the conditional the pattern exists to remove.