Factory Method Pattern
Explain Like I'm 5...
Imagine you have a toy factory! When someone asks for a toy, the factory decides which toy to make - maybe a car, a doll, or a robot.
The Toy Factory:
- • You tell the factory: 'I want a vehicle toy!'
- • The factory boss decides: 'Okay, I'll make a car!' or 'I'll make a bike!'
- • You don't need to know HOW to make the toy - the factory does it for you!
- • Different factories can make different types of toys, but they all work the same way!
Why Is This Useful?
- • You don't need to know the secret recipe for making each toy!
- • The factory can decide which toy to make based on what you need!
- • If you want to add new toys, you just teach the factory - you don't change your request!
What is Factory Method Pattern?
The Factory Method pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. Instead of calling a constructor directly, you call a factory method that returns the object.
Purpose
- • Define an interface for creating an object, but let subclasses decide which class to instantiate
- • Let a class defer instantiation to subclasses
- • Provide flexibility in deciding which objects to create
Key Components
1. Product: Product: The interface/abstract class for objects the factory method creates
2. ConcreteProduct: ConcreteProduct: Specific implementations of the Product interface
3. Creator: Creator: Declares the factory method that returns Product objects
4. ConcreteCreator: ConcreteCreator: Overrides factory method to return ConcreteProduct instances
Implementation 1: Shape Factory
A classic example where different shape factories create different shape objects.
// Product interface - defines what all shapes must dopublic interface Shape { void draw(); double calculateArea();}// ConcreteProduct - specific implementation of Shapepublic class Circle implements Shape { private double radius; public Circle(double radius) { this.radius = radius; } @Override public void draw() { System.out.println("Drawing a Circle with radius: " + radius); } @Override public double calculateArea() { return Math.PI * radius * radius; }}// ConcreteProduct - another implementation of Shapepublic class Rectangle implements Shape { private double width; private double height; public Rectangle(double width, double height) { this.width = width; this.height = height; } @Override public void draw() { System.out.println("Drawing a Rectangle: " + width + "x" + height); } @Override public double calculateArea() { return width * height; }}// ConcreteProduct - square is a special rectanglepublic class Square implements Shape { private double side; public Square(double side) { this.side = side; } @Override public void draw() { System.out.println("Drawing a Square with side: " + side); } @Override public double calculateArea() { return side * side; }}// Creator - abstract factory classpublic abstract class ShapeFactory { // Factory method - subclasses will override this public abstract Shape createShape(); // Template method that uses the factory method public void renderShape() { // Use the factory method to get the shape Shape shape = createShape(); // Common operations for all shapes System.out.println("--- Rendering Shape ---"); shape.draw(); System.out.println("Area: " + shape.calculateArea()); System.out.println("----------------------"); }}// ConcreteCreator - creates Circle objectspublic class CircleFactory extends ShapeFactory { private double radius; public CircleFactory(double radius) { this.radius = radius; } @Override public Shape createShape() { // This factory always creates Circle objects return new Circle(radius); }}// ConcreteCreator - creates Rectangle objectspublic class RectangleFactory extends ShapeFactory { private double width; private double height; public RectangleFactory(double width, double height) { this.width = width; this.height = height; } @Override public Shape createShape() { // This factory always creates Rectangle objects return new Rectangle(width, height); }}// Client code - uses factories to create shapespublic class ShapeFactoryDemo { public static void main(String[] args) { // Create different factories ShapeFactory circleFactory = new CircleFactory(5.0); ShapeFactory rectangleFactory = new RectangleFactory(4.0, 6.0); // Use factories to create and render shapes // The client doesn't need to know which concrete class is created System.out.println("Creating and rendering shapes:"); circleFactory.renderShape(); rectangleFactory.renderShape(); // We can easily add more shape types without changing client code ShapeFactory squareFactory = new ShapeFactory() { @Override public Shape createShape() { return new Square(7.0); } }; squareFactory.renderShape(); }}/* Output:Creating and rendering shapes:--- Rendering Shape ---Drawing a Circle with radius: 5.0Area: 78.53981633974483------------------------- Rendering Shape ---Drawing a Rectangle: 4.0x6.0Area: 24.0------------------------- Rendering Shape ---Drawing a Square with side: 7.0Area: 49.0----------------------*/Implementation 2: Document Factory
Creating different types of documents (PDF, Word, Excel) using factory method.
// Product interface - all documents must implement these methodspublic interface Document { void open(); void save(); void close(); String getType();}// ConcreteProduct - PDF implementationpublic class PDFDocument implements Document { private String fileName; public PDFDocument(String fileName) { this.fileName = fileName; } @Override public void open() { System.out.println("Opening PDF document: " + fileName); System.out.println("Loading PDF renderer..."); } @Override public void save() { System.out.println("Saving PDF document with compression..."); } @Override public void close() { System.out.println("Closing PDF document: " + fileName); } @Override public String getType() { return "PDF"; }}// ConcreteProduct - Word document implementationpublic class WordDocument implements Document { private String fileName; public WordDocument(String fileName) { this.fileName = fileName; } @Override public void open() { System.out.println("Opening Word document: " + fileName); System.out.println("Loading MS Word engine..."); } @Override public void save() { System.out.println("Saving Word document in .docx format..."); } @Override public void close() { System.out.println("Closing Word document: " + fileName); } @Override public String getType() { return "Word"; }}// ConcreteProduct - Excel spreadsheet implementationpublic class ExcelDocument implements Document { private String fileName; public ExcelDocument(String fileName) { this.fileName = fileName; } @Override public void open() { System.out.println("Opening Excel document: " + fileName); System.out.println("Loading spreadsheet engine..."); } @Override public void save() { System.out.println("Saving Excel document with formulas..."); } @Override public void close() { System.out.println("Closing Excel document: " + fileName); } @Override public String getType() { return "Excel"; }}// Creator - abstract factory for documentspublic abstract class DocumentFactory { // Factory method - to be implemented by subclasses public abstract Document createDocument(String fileName); // Template method using the factory method public void processDocument(String fileName) { // Create document using factory method Document doc = createDocument(fileName); // Perform common operations System.out.println("\n=== Processing " + doc.getType() + " Document ==="); doc.open(); // Simulate some work System.out.println("... working on document ..."); doc.save(); doc.close(); System.out.println("=== Done ===\n"); }}// ConcreteCreators - specific factory implementationspublic class PDFDocumentFactory extends DocumentFactory { @Override public Document createDocument(String fileName) { return new PDFDocument(fileName); }}public class WordDocumentFactory extends DocumentFactory { @Override public Document createDocument(String fileName) { return new WordDocument(fileName); }}public class ExcelDocumentFactory extends DocumentFactory { @Override public Document createDocument(String fileName) { return new ExcelDocument(fileName); }}// Client code demonstrating document creationpublic class DocumentFactoryDemo { public static void main(String[] args) { // Create different document factories DocumentFactory pdfFactory = new PDFDocumentFactory(); DocumentFactory wordFactory = new WordDocumentFactory(); DocumentFactory excelFactory = new ExcelDocumentFactory(); // Process different types of documents // The client code doesn't need to know the concrete document classes pdfFactory.processDocument("report.pdf"); wordFactory.processDocument("letter.docx"); excelFactory.processDocument("budget.xlsx"); // Example: Document processor that works with any factory processMultipleDocuments(pdfFactory, new String[]{"doc1.pdf", "doc2.pdf"}); } // This method demonstrates polymorphism // It works with any DocumentFactory subclass private static void processMultipleDocuments( DocumentFactory factory, String[] fileNames) { System.out.println("Batch processing documents..."); for (String fileName : fileNames) { factory.processDocument(fileName); } }}/* Output:=== Processing PDF Document ===Opening PDF document: report.pdfLoading PDF renderer...... working on document ...Saving PDF document with compression...Closing PDF document: report.pdf=== Done ====== Processing Word Document ===Opening Word document: letter.docxLoading MS Word engine...... working on document ...Saving Word document in .docx format...Closing Word document: letter.docx=== Done ===...*/Implementation 3: Transportation Factory
Creating different vehicles for transportation logistics.
// Product interface - common interface for all transport typespublic interface Transport { void deliver(String destination, int packages); String getTransportType(); double calculateCost(int distance);}// ConcreteProduct - Truck implementationpublic class Truck implements Transport { private static final double COST_PER_KM = 2.5; @Override public void deliver(String destination, int packages) { System.out.println("Delivering " + packages + " packages by TRUCK"); System.out.println("Destination: " + destination); System.out.println("Loading packages into truck container..."); System.out.println("Driving on roads..."); } @Override public String getTransportType() { return "Truck"; } @Override public double calculateCost(int distance) { return distance * COST_PER_KM; }}// ConcreteProduct - Ship implementationpublic class Ship implements Transport { private static final double COST_PER_KM = 1.5; @Override public void deliver(String destination, int packages) { System.out.println("Delivering " + packages + " packages by SHIP"); System.out.println("Destination: " + destination); System.out.println("Loading packages into containers..."); System.out.println("Sailing across the ocean..."); } @Override public String getTransportType() { return "Ship"; } @Override public double calculateCost(int distance) { // Ships are cheaper per km but have a base port fee return (distance * COST_PER_KM) + 500; // 500 is port fee }}// ConcreteProduct - Plane implementationpublic class Plane implements Transport { private static final double COST_PER_KM = 5.0; @Override public void deliver(String destination, int packages) { System.out.println("Delivering " + packages + " packages by PLANE"); System.out.println("Destination: " + destination); System.out.println("Loading packages into aircraft cargo..."); System.out.println("Flying through the sky..."); } @Override public String getTransportType() { return "Plane"; } @Override public double calculateCost(int distance) { // Planes are expensive but fast return distance * COST_PER_KM; }}// Creator - abstract logistics companypublic abstract class Logistics { // Factory method - subclasses decide which transport to create public abstract Transport createTransport(); // Template method for planning delivery public void planDelivery(String destination, int packages, int distance) { // Create appropriate transport using factory method Transport transport = createTransport(); System.out.println("\n===== Delivery Planning ====="); System.out.println("Transport Type: " + transport.getTransportType()); System.out.println("Distance: " + distance + " km"); // Calculate cost double cost = transport.calculateCost(distance); System.out.println("Estimated Cost: $" + cost); // Execute delivery transport.deliver(destination, packages); System.out.println("Delivery completed!"); System.out.println("============================\n"); }}// ConcreteCreators - specific logistics companies// Road logistics - uses truckspublic class RoadLogistics extends Logistics { @Override public Transport createTransport() { return new Truck(); }}// Sea logistics - uses shipspublic class SeaLogistics extends Logistics { @Override public Transport createTransport() { return new Ship(); }}// Air logistics - uses planespublic class AirLogistics extends Logistics { @Override public Transport createTransport() { return new Plane(); }}// Client code demonstrating logistics systempublic class TransportationDemo { public static void main(String[] args) { // Based on requirements, choose appropriate logistics // Scenario 1: Domestic delivery (short distance) System.out.println("Scenario 1: Domestic delivery"); Logistics domesticLogistics = new RoadLogistics(); domesticLogistics.planDelivery("New York", 50, 200); // Scenario 2: International delivery (long distance, many packages) System.out.println("Scenario 2: International shipping"); Logistics internationalLogistics = new SeaLogistics(); internationalLogistics.planDelivery("London", 1000, 5000); // Scenario 3: Express delivery (urgent) System.out.println("Scenario 3: Express delivery"); Logistics expressLogistics = new AirLogistics(); expressLogistics.planDelivery("Tokyo", 20, 10000); // Demonstrate flexibility: runtime decision System.out.println("\nDynamic decision based on distance:"); int distance = 300; Logistics chosenLogistics = selectLogistics(distance); chosenLogistics.planDelivery("Chicago", 75, distance); } // Factory method to select logistics based on business rules private static Logistics selectLogistics(int distance) { if (distance < 500) { System.out.println("Selected: Road logistics (short distance)"); return new RoadLogistics(); } else if (distance < 3000) { System.out.println("Selected: Air logistics (medium distance)"); return new AirLogistics(); } else { System.out.println("Selected: Sea logistics (long distance)"); return new SeaLogistics(); } }}/* Output:Scenario 1: Domestic delivery===== Delivery Planning =====Transport Type: TruckDistance: 200 kmEstimated Cost: $500.0Delivering 50 packages by TRUCKDestination: New YorkLoading packages into truck container...Driving on roads...Delivery completed!============================...*/Real-World Examples
UI Component Frameworks
Frameworks like React or Android use factory methods to create different UI components based on configuration
Notification Systems
Email, SMS, or Push notification creators that all implement the same interface but create different notification types
Payment Gateways
PayPal, Stripe, or Credit Card payment processors where the factory creates the appropriate payment handler
Database Connections
JDBC drivers use factory methods to create connections for different database types (MySQL, PostgreSQL, etc.)
When to Use Factory Method
- ✓When you don't know beforehand the exact types and dependencies of objects your code should work with
- ✓When you want to provide a way to extend internal components
- ✓When you want to save system resources by reusing existing objects instead of rebuilding them
- ✓When you want to delegate the responsibility of creating objects to helper subclasses
UML Diagram Explanation
The Factory Method pattern structure:
- • Creator class declares the factory method that must return an object of a Product class
- • ConcreteCreator overrides the factory method to return different types of products
- • Product defines the interface that all products must implement
- • ConcreteProducts are different implementations of the Product interface
Benefits
- +Loose Coupling: Code depends on interfaces, not concrete classes
- +Single Responsibility Principle: Object creation code is in one place
- +Open/Closed Principle: New product types without changing existing code
- +Flexibility: Easy to add new product types by creating new factory subclasses
- +Code Reusability: Common object creation logic in base factory class
Drawbacks
- -Code Complexity: Pattern requires many new subclasses
- -Overhead: May be overkill for simple object creation
- -Learning Curve: Team members need to understand the pattern
Key Points to Remember
- 1Factory Method is about polymorphism - using inheritance and overriding
- 2Promotes loose coupling by eliminating the need to bind application classes to concrete classes
- 3Follows Open/Closed Principle - open for extension, closed for modification
- 4The factory method can have default implementation in the base class
- 5Subclasses decide which concrete class to instantiate
Practice Scenarios
- • Create a Logger factory that creates FileLogger, ConsoleLogger, or DatabaseLogger
- • Implement a Vehicle Rental System with factories for different vehicle types
- • Build a Report Generator that creates PDF, Excel, or HTML reports
- • Design a Button factory for different operating systems (Windows, Mac, Linux)
- • Create a Database Connection factory supporting multiple database types