DEV Community

DevCorner2
DevCorner2

Posted on

🏭 Factory Design Pattern in Java β€” 3 Scalable Ways to Future-Proof Your Code

The Factory Design Pattern is a creational design pattern that helps decouple object creation from usage. But in real-world systems, we often ask:

"What if I need to add more types laterβ€”without modifying the factory itself?"

In this post, you'll learn three extensible ways to implement the Factory Pattern in Java, starting from the classic version and evolving toward more open/closed, pluggable, and automatically discovered solutions.


πŸ”§ 1. Classic Factory (Not Extensible)

Let’s start with the traditional version where a factory creates shapes based on a string type.

πŸ“„ Shape.java

public interface Shape {
    void draw();
}
Enter fullscreen mode Exit fullscreen mode

πŸ“„ Circle.java

public class Circle implements Shape {
    public void draw() {
        System.out.println("Inside Circle::draw() method.");
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“„ ShapeFactory.java

public class ShapeFactory {
    public Shape getShape(String shapeType) {
        if (shapeType.equalsIgnoreCase("CIRCLE")) return new Circle();
        if (shapeType.equalsIgnoreCase("RECTANGLE")) return new Rectangle();
        if (shapeType.equalsIgnoreCase("SQUARE")) return new Square();
        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

❌ Limitation

  • You must modify ShapeFactory to add a new shape.
  • Violates the Open/Closed Principle.

βœ… 2. Registry-Based Factory (More Extensible)

To avoid modifying the factory, we'll create a registry that stores a mapping from shape name to constructor.

βœ… Benefits

  • No need to change the factory when adding new shapes.
  • Shapes register themselves statically.

πŸ“„ Shape.java

public interface Shape {
    void draw();
}
Enter fullscreen mode Exit fullscreen mode

πŸ“„ ShapeFactory.java

import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;

public class ShapeFactory {
    private static final Map<String, Supplier<Shape>> registry = new HashMap<>();

    public static void registerShape(String name, Supplier<Shape> supplier) {
        registry.put(name.toLowerCase(), supplier);
    }

    public static Shape getShape(String name) {
        Supplier<Shape> supplier = registry.get(name.toLowerCase());
        if (supplier != null) return supplier.get();
        throw new IllegalArgumentException("Unknown shape: " + name);
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“„ Circle.java

public class Circle implements Shape {
    static {
        ShapeFactory.registerShape("circle", Circle::new);
    }

    public void draw() {
        System.out.println("Inside Circle::draw() method.");
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“„ Main.java

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("Circle");
        Shape circle = ShapeFactory.getShape("circle");
        circle.draw();
    }
}
Enter fullscreen mode Exit fullscreen mode

🧠 Note:

You must explicitly load each shape class (Class.forName(...)) to trigger static registration.


πŸš€ 3. Using Java ServiceLoader (Auto Discovery)

Let’s automate shape discovery using Java’s built-in ServiceLoader. It reads implementations declared in META-INF/services.

βœ… Benefits

  • Add new shapes by just implementing the interface and updating a config file.
  • No manual registration or modification needed.

πŸ“„ Shape.java

public interface Shape {
    void draw();
    String getName();
}
Enter fullscreen mode Exit fullscreen mode

πŸ“„ Circle.java

public class Circle implements Shape {
    public void draw() {
        System.out.println("Inside Circle::draw() method.");
    }

    public String getName() {
        return "circle";
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“„ META-INF/services/Shape

Create this file in resources:

Circle
Rectangle
Square
Enter fullscreen mode Exit fullscreen mode

Each line is a fully qualified class name of a shape.


πŸ“„ ShapeFactory.java

import java.util.*;
import java.util.function.Supplier;

public class ShapeFactory {
    private static final Map<String, Supplier<Shape>> registry = new HashMap<>();

    static {
        ServiceLoader<Shape> loader = ServiceLoader.load(Shape.class);
        for (Shape shape : loader) {
            String name = shape.getName().toLowerCase();
            registry.put(name, () -> {
                try {
                    return shape.getClass().getDeclaredConstructor().newInstance();
                } catch (Exception e) {
                    throw new RuntimeException("Could not instantiate shape: " + name, e);
                }
            });
        }
    }

    public static Shape getShape(String name) {
        Supplier<Shape> supplier = registry.get(name.toLowerCase());
        if (supplier != null) return supplier.get();
        throw new IllegalArgumentException("Shape not registered: " + name);
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“„ Main.java

public class Main {
    public static void main(String[] args) {
        Shape shape = ShapeFactory.getShape("circle");
        shape.draw();
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… Summary Table

Approach Extensible? Auto-discovery No Factory Changes? Manual Setup Needed
Classic Factory ❌ No ❌ No ❌ No ❌ None
Registry Pattern βœ… Yes ❌ No βœ… Yes βœ… Class.forName()
ServiceLoader βœ… Yes βœ… Yes βœ… Yes βœ… META-INF/services

🧩 Bonus: Plugin-Like Shape System

Using ServiceLoader, your shape system behaves like a plugin architecture. You can even load new shapes from external jars at runtime, making this approach great for extensible frameworks and toolkits.


✍️ Final Thoughts

If you're building a simple app, the classic factory works. But if you’re designing for extensibility, especially in a large codebase or plugin-like architecture, go for the ServiceLoader or Registry-Based Factory.

Let your factories scale without becoming a God-class.


Want the same pattern implemented in Spring or with third-party libraries like Reflections? Let me know and I’ll walk you through that too.

Top comments (0)