June 17, 2026 in patterns5 minutes
A intermediate-level guide to Factory Method: before-and-after java code and diagrams for a CS student.
When you start building features, it often feels natural to instantiate objects exactly where you need them. If your program needs a WordDocument, you write new WordDocument(). This works fine until your requirements grow. Suddenly, your editor needs to handle PDFs too. Then, perhaps, Excel files or HTML files.
Soon, your core logic becomes cluttered with conditional checks to decide which specific class to instantiate. This is a “Switch Statements” code smell—a type-dispatching conditional that creates tight coupling. Coupling describes the strength of the link between two components; here, DocumentEditor is so tightly coupled to concrete types like WordDocument and PdfDocument that it cannot function without knowing they exist.
interface Document {
void open();
}
class WordDocument implements Document {
public void open() { System.out.println("Opening Word document..."); }
}
class PdfDocument implements Document {
public void open() { System.out.println("Opening PDF document..."); }
}
class DocumentEditor {
// Problem: The editor is tightly coupled to concrete implementations.
// Adding a new document type requires modifying this switch block.
public Document createDocument(String type) {
if (type.equals("WORD")) {
return new WordDocument();
} else if (type.equals("PDF")) {
return new PdfDocument();
}
throw new IllegalArgumentException("Unknown type: " + type);
}
}In this design, every time a new document type is added to the system, you are forced to modify the createDocument method in DocumentEditor. This violates the Open-Closed Principle, which suggests software entities should be open for extension but closed for modification. The editor “knows” too much about the specific implementation details of every possible document type.
classDiagram
class Document {
<<interface>>
+open() void
}
class WordDocument {
+open() void
}
class PdfDocument {
+open() void
}
class DocumentEditor {
+createDocument(String) Document
}
WordDocument ..|> Document
PdfDocument ..|> Document
DocumentEditor ..> WordDocument : dependency
DocumentEditor ..> PdfDocument : dependency
To fix this, we use a creational design pattern called the Factory Method. Instead of the DocumentEditor deciding which object to create using a switch statement, we delegate that responsibility to its subclasses.
The “Factory Method” is actually an abstract method (or an empty placeholder) defined in a base class. The base class handles the high-level logic, but it leaves the actual instantiation to the subclasses via this method. This allows the core business logic to remain completely independent of the specific products being created.
interface Document {
void open();
}
class WordDocument implements Document {
public void open() { System.out.println("Opening Word document..."); }
}
class PdfDocument implements Document {
public void open() { System.out.println("Opening PDF document..."); }
}
abstract class DocumentEditor {
// The Factory Method: delegating instantiation to subclasses.
protected abstract Document createDocument();
public void edit() {
Document doc = createDocument();
doc.open();
}
}
class WordEditor extends DocumentEditor {
@Override
protected Document createDocument() {
return new WordDocument();
}
}
class PdfEditor extends DocumentEditor {
@Override
protected Document createDocument() {
return new PdfDocument();
}
}In the refactored version, DocumentEditor is now an abstract class. It defines a “hook” called createDocument(). Notice that the edit() method no longer cares if it is working with a Word or PDF file; it simply calls createDocument() and then tells the resulting object to open().
The actual decision of which document to create has moved down the inheritance hierarchy. The WordEditor subclass implements the factory method by returning a WordDocument, while the PdfEditor does the same for PDF files.
sequenceDiagram
participant E as WordEditor
participant DE as DocumentEditor
participant D as WordDocument
E ->> DE: edit()
DE ->> E: createDocument()
E -->> DE: return new WordDocument()
DE ->> D: open()
The primary benefit of this pattern is that it breaks the direct dependency between the high-level logic and the low-level concrete classes. The DocumentEditor now only depends on the Document interface.
By using inheritance to handle creation, you achieve “Replace Conditional with Polymorphism.” Instead of a switch statement checking a string to decide which path to take, the program uses the specialized behavior of the subclass itself to drive the logic. This makes your system more extensible; adding a new document type simply involves creating one new class for the product and one new creator subclass, without ever touching your existing, tested editor logic.
classDiagram
class Document {
<<interface>>
+open() void
}
class WordDocument {
+open() void
}
class PdfDocument {
+open() void
}
class DocumentEditor {
<<abstract>>
#createDocument()* Document
+edit() void
}
class WordEditor {
#createDocument() Document
}
class PdfEditor {
#createDocument() Document
}
WordDocument ..|> Document
PdfDocument ..|> Document
DocumentEditor <|-- WordEditor
DocumentEditor <|-- PdfEditor
WordEditor ..> WordDocument : creates
PdfEditor ..> PdfDocument : creates
The Factory Method is powerful, but it is not a silver bullet.
Use it when:
Avoid it when:
new keyword to call.The client uses a specific subclass to get the desired behavior without manual type checking.
DocumentEditor editor = new WordEditor();
editor.edit();