Tutorial: Factory Method Pattern in Java
The Factory Method is a creational design pattern that allows a class to defer the instantiation of objects to its subclasses. Instead of using the new keyword to create a specific object, you call a method that handles the creation.
The Problem
Suppose you are building a Document Management System for your website. Initially, your system only supports Plain Text files. Your code is filled with logic specifically for TextDocument classes.
Later, you want to support PDF and Markdown documents. If you used new TextDocument() everywhere in your code, adding new types would require you to find and change every single instantiation point. This leads to code that is hard to maintain and prone to errors.
The Solution: Factory Method
We create a "Creator" class that declares a method for creating objects. Subclasses will override this method to return the specific type of document needed.
Step-by-Step Java Implementation
1. Define the Product Interface
Every document, regardless of its format, should be able to open and save.
// Document.java
public interface Document {
void open();
void save();
}
2. Create Concrete Products
These are the specific implementations for each file type.
// TextDocument.java
public class TextDocument implements Document {
@Override
public void open() {
System.out.println("Opening Plain Text document...");
}
@Override
public void save() {
System.out.println("Saving Plain Text document...");
}
}
// PdfDocument.java
public class PdfDocument implements Document {
@Override
public void open() {
System.out.println("Opening PDF document with Adobe Reader...");
}
@Override
public void save() {
System.out.println("Exporting and saving PDF file...");
}
}
3. Create the Creator (The Factory)
This class contains the logic that relies on the documents. It declares the Factory Method (createDocument) as abstract.
// DocumentManager.java
public abstract class DocumentManager {
// The Factory Method
public abstract Document createDocument();
// Core business logic that uses the product
public void manageDocument() {
Document doc = createDocument();
doc.open();
doc.save();
}
}
4. Create Concrete Creators
Each manager "decides" which document it handles.
// TextManager.java
public class TextManager extends DocumentManager {
@Override
public Document createDocument() {
return new TextDocument();
}
}
// PdfManager.java
public class PdfManager extends DocumentManager {
@Override
public Document createDocument() {
return new PdfDocument();
}
}
5. Executing the Code
Your website's backend code (the Client) can now work with any manager without knowing which specific document is being created.
// WebsiteApp.java
public class WebsiteApp {
private static DocumentManager manager;
public static void main(String[] args) {
// This could be determined by a user selection or file extension
String userSelection = "PDF";
if (userSelection.equalsIgnoreCase("TEXT")) {
manager = new TextManager();
} else if (userSelection.equalsIgnoreCase("PDF")) {
manager = new PdfManager();
}
// The app doesn't care which document it is!
manager.manageDocument();
}
}
Why this is better:
- Loose Coupling: The
WebsiteAppdoesn't need to know aboutPdfDocumentorTextDocumentclasses. It only knows about theDocumentManagerand theDocumentinterface. - Scalability: To add Markdown support, you simply create a
MarkdownDocumentand aMarkdownManager. You don't have to touch the existing code inWebsiteAppor the other managers. - Consistency: All creation logic for a specific type is encapsulated in its own manager.
full code :
// Save this file as DocumentTest.java
// 1. The Product Interface
interface Document {
void open();
void save();
}
// 2. Concrete Products
class TextDocument implements Document {
@Override
public void open() {
System.out.println("[Text] Opening Plain Text document...");
}
@Override
public void save() {
System.out.println("[Text] Saving changes to .txt file...");
}
}
class PdfDocument implements Document {
@Override
public void open() {
System.out.println("[PDF] Opening PDF with internal viewer...");
}
@Override
public void save() {
System.out.println("[PDF] Flattening layers and exporting to .pdf...");
}
}
// 3. The Creator (Abstract Factory Class)
abstract class DocumentManager {
// This is the Factory Method
public abstract Document createDocument();
// Business logic that works with the Product
public void processDocument() {
Document doc = createDocument();
doc.open();
doc.save();
}
}
// 4. Concrete Creators
class TextManager extends DocumentManager {
@Override
public Document createDocument() {
return new TextDocument();
}
}
class PdfManager extends DocumentManager {
@Override
public Document createDocument() {
return new PdfDocument();
}
}
// 5. The Main Test Class
public class DocumentTest {
public static void main(String[] args) {
System.out.println("--- Starting Document Management System ---\n");
// Scenario 1: User wants to work with Text
System.out.println("User selects: Text Mode");
DocumentManager textApp = new TextManager();
textApp.processDocument();
System.out.println("\n-------------------------------------------\n");
// Scenario 2: User wants to work with PDF
System.out.println("User selects: PDF Mode");
DocumentManager pdfApp = new PdfManager();
pdfApp.processDocument();
System.out.println("\n--- Operations Completed Successfully ---");
}
}