SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignFactory Pattern
Software DesignCreational Patterns

Factory Pattern

Create objects without exposing the client to the concrete implementation being instantiated.

October 1, 20244 min read
design-patternsjavafactorycreational

One-line Definition

Encapsulates object creation so clients depend on abstractions instead of concrete classes.


Problem

Suppose your application supports multiple payment methods such as Credit Card, UPI, and PayPal. If client code directly creates these classes using new, every new payment type requires changes throughout the application. The Factory Pattern moves object creation into a dedicated factory so client code depends only on the interface.


Real-world Analogy

You walk into a pizza shop and say "I'd like a pizza." The shop doesn't send you into the kitchen — it asks "Margherita? Pepperoni? Veggie?" and hands you the finished pizza. You never see how it's made. The factory (kitchen) handles all creation logic behind the scenes.


Structure

  • Client
    • Requests objects from the factory and works only with the Product interface.
  • Factory
    • Creates and returns the appropriate concrete product.
  • Product
    • Common interface shared by all products.
  • Concrete Products
    • Implement the Product interface.

Diagram

classDiagram
    class Client {
    }
    class ShapeFactory {
        +getShape(type: String): Shape
    }
    class Shape {
        <<interface>>
        +draw()
    }
    class Circle {
        +draw()
    }
    class Square {
        +draw()
    }

    Client --> ShapeFactory
    Client --> Shape
    ShapeFactory ..> Circle : creates
    ShapeFactory ..> Square : creates
    Circle ..|> Shape
    Square ..|> Shape

Code Walkthrough

Notice that Main (the client) depends only on the Shape interface. The factory decides which implementation (Circle or Square) to create.

interface Shape {
    void draw();
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Circle");
    }
}

class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Square");
    }
}

class ShapeFactory {
    public Shape getShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("CIRCLE")) {
            return new Circle();
        } else if (shapeType.equalsIgnoreCase("SQUARE")) {
            return new Square();
        }
        return null;
    }
}

class Main {
    public static void main(String[] args) {
        ShapeFactory shapeFactory = new ShapeFactory();
        Shape shape1 = shapeFactory.getShape("CIRCLE");
        shape1.draw();
        Shape shape2 = shapeFactory.getShape("SQUARE");
        shape2.draw();
    }
}

Bad vs Good

Bad Approach

Problems

  • LogisticsService is tightly coupled to concrete classes.
  • Adding a new mode breaks the Open/Closed Principle.
  • Hard to test independently.
interface Logistics {
    void send();
}

class Road implements Logistics {
    @Override
    public void send() {
        System.out.println("Sending by road logic");
    }
}

class Air implements Logistics {
    @Override
    public void send() {
        System.out.println("Sending by air logic");
    }
}

class LogisticsService {
    public void send(String mode) {
        if (mode.equals("Air")) {
            Logistics logistics = new Air();
            logistics.send();
        } else if (mode.equals("Road")) {
            Logistics logistics = new Road();
            logistics.send();
        }
    }
}

class Main {
    public static void main(String[] args) {
        LogisticsService service = new LogisticsService();
        service.send("Air");
        service.send("Road");
    }
}

Better Approach

Improvements

  • New modes are added by extending the factory, keeping business logic unchanged.
  • Object creation is centralized.
interface Logistics {
    void send();
}

class Road implements Logistics {
    @Override
    public void send() {
        System.out.println("Sending by road logic");
    }
}

class Air implements Logistics {
    @Override
    public void send() {
        System.out.println("Sending by air logic");
    }
}

class LogisticsFactory {
    public static Logistics getLogistics(String mode) {
        if (mode.equalsIgnoreCase("Air")) {
            return new Air();
        } else if (mode.equalsIgnoreCase("Road")) {
            return new Road();
        }
        throw new IllegalArgumentException("Unknown logistics mode: " + mode);
    }
}

class LogisticsService {
    public void send(String mode) {
        Logistics logistics = LogisticsFactory.getLogistics(mode);
        logistics.send();
    }
}

class Main {
    public static void main(String[] args) {
        LogisticsService service = new LogisticsService();
        service.send("Air");
        service.send("Road");
    }
}

Pros vs Cons

ProsCons
Decouples client code from concrete classesMore classes to maintain
Follows the Open/Closed PrincipleCan be unnecessary for simple applications
Centralizes object creationMay introduce an extra abstraction layer
Runtime flexibilityFactory logic can grow complex if not refactored
Reduces duplicate instantiation logicObscures exact object types from the client

When to Use

  • Multiple implementations share a common interface.
  • The concrete implementation is chosen at runtime.
  • You want to reduce coupling between clients and implementations.
  • Object creation involves more than a simple constructor call.

When Not to Use

  • Only one implementation exists.
  • Object creation is trivial.
  • A constructor is sufficient.
  • Adding a factory would only increase complexity.

Real-world Examples

  • java.util.Calendar.getInstance()
  • java.util.ResourceBundle.getBundle()
  • java.text.NumberFormat.getInstance()
  • java.nio.charset.Charset.forName()
  • Spring Framework's BeanFactory

Key Takeaway

The Factory Pattern separates object creation from object usage. Clients request an abstraction, while the factory decides which concrete implementation to return. Use it when implementations vary at runtime, but avoid introducing a factory when a simple constructor is enough.

PreviousBridge PatternNextDecorator Pattern