Factory Method Pattern
THE WHAT
The Factory Method is a way to create objects without specifying their exact class (or using the new operator in the client). Instead, you have a base class with a method for creating objects, and subclasses of this base class decide what specific type of object to create. This makes it easy to choose or switch the type of object being created without changing the main code that uses them.
In other words…
The Factory Method is like a tool that helps you make different types of products. You have a main tool (base class) that knows how to make a product, but the details of what specific product to make are decided by different versions of the tool (subclasses). This way, you can add new types of products without changing the main tool.
The General Problem it tries to solve
The Factory Method addresses the scenarios where the exact type of object isn’t known until runtime, or when the creation process is complex and should be centralized to manage changes more effectively.
Example of the General Problem
Consider a software system that manages various types of documents. Initially, it supports text documents and spreadsheets. As the system evolves, new document types, such as presentations, are added. Hardcoding the creation logic for each document type within the client code leads to tight coupling and makes the system difficult to maintain and extend. The Factory Method provides a way to decouple the client code from the specific classes it instantiates, allowing new document types to be added with minimal changes to the existing code.
THE WHEN
You should consider using the Factory Method when:
THE HOW
public interface Document {
void open();
void close();
void save();
}
2. Create Concrete Products: Implement different versions of the product interface.
public class TextDocument implements Document {
@Override
public void open() { /* ... */ }
@Override
public void close() { /* ... */ }
@Override
public void save() { /* ... */ }
}
public class SpreadsheetDocument implements Document {
@Override
public void open() { /* ... */ }
@Override
public void close() { /* ... */ }
@Override
public void save() { /* ... */ }
}
public class PresentationDocument implements Document {
@Override
public void open() { /* ... */ }
@Override
public void close() { /* ... */ }
@Override
public void save() { /* ... */ }
}
3. Define a Creator Class: Declare a factory method that returns new product objects. The return type of this method should match the product interface.
public abstract class DocumentViewer {
// Factory method
public abstract Document createDocument();
public void openDocument() {
Document doc = createDocument();
doc.open();
// additional operations
}
public void closeDocument() {
Document doc = createDocument();
doc.close();
// additional operations
}
public void saveDocument() {
Document doc = createDocument();
doc.save();
// additional operations
}
}
4. Create Concrete Creators: Subclass the creator class and override the factory method to return different types of products.
Recommended by LinkedIn
public class TextDocumentViewer extends DocumentViewer {
@Override
public Document createDocument() {
return new TextDocument();
}
}
public class SpreadDocumentViewer extends DocumentViewer {
@Override
public Document createDocument() {
return new SpreadsheetDocument();
}
}
public class PresentationDocumentViewer extends DocumentViewer {
@Override
public Document createDocument() {
return new PresentationDocument();
}
}
5. Create a Factory Class: Create a factory class to bind it all together.
public class DocumentViewerFactory {
public static DocumentViewer getDocumentViewer(String type) {
switch (type.toLowerCase()) {
case "text":
return new TextDocumentViewer();
case "spreadsheet":
return new SpreadsheetDocumentViewer();
case "presentation":
return new PresentationDocumentViewer();
default:
throw new IllegalArgumentException("Unknown document type: " + type);
}
}
}
public class Main {
public static void main(String[] args) {
// Example usage of the factory
DocumentViewer textViewer = DocumentViewerFactory.getDocumentViewer("text");
textViewer.openDocument();
textViewer.saveDocument();
textViewer.closeDocument();
DocumentViewer spreadsheetViewer = DocumentViewerFactory.getDocumentViewer("spreadsheet");
spreadsheetViewer.openDocument();
spreadsheetViewer.saveDocument();
spreadsheetViewer.closeDocument();
DocumentViewer presentationViewer = DocumentViewerFactory.getDocumentViewer("presentation");
presentationViewer.openDocument();
presentationViewer.saveDocument();
presentationViewer.closeDocument();
}
}
UML Diagram
GENERIC UML DIAGRAM
THE CONSEQUENCES
Pros
Cons
The Tradeoffs
While the Factory Method pattern provides flexibility and decouples the client code from specific classes, it also introduces additional complexity. This complexity is a tradeoff for the maintainability and extensibility gains. Therefore, it's crucial to evaluate whether the benefits outweigh the costs in your particular context.
Conclusion
The Factory Method pattern is a powerful tool for creating objects in a flexible and maintainable way. By encapsulating the object creation process, it adheres to key design principles, making it easier to extend and manage code. However, it's essential to balance its benefits against the complexity it introduces, ensuring that its use adds value to your project.
Thank you