Simple explanation for Factory Pattern

Situation: You’re Creating Notification Objects

You’re building a feature that sends notifications.

Initially, you have just one type: EmailNotification.

You create it like this:

= new EmailNotification(to, subject, body)

All cool.

Then the Feature Grows

Now you also have:

  • SMSNotification
  • PushNotification
  • SlackNotification
  • WhatsAppNotification

Each type needs different parameters, different setup logic.

You’re stuck writing code like:

if (type == "email") new EmailNotification(...)
else if (type == "sms") new SMSNotification(...)
else if (type == "push") new PushNotification(...)
...

Everywhere.

What’s the Problem?

  • This logic is repeated all over your codebase.
  • Every time you add a new type, you have to change existing code - breaking the Open/Closed Principle.
  • Code becomes tightly coupled to specific classes.
  • It’s a testing nightmare and hard to maintain.

How Factory Pattern Saves You

Instead of creating objects directly or writing switch/if chains, you create a NotificationFactory.

You say: “Hey factory, give me a notification object of type X.”

And the factory:

  • Hides the creation logic
  • Decides which subclass to return
  • Keeps your main code clean and decoupled

Now you don’t care how it creates it. You just ask and get a ready-to-use object.

class NotificationFactory {
    private static final Map<String, Supplier<Notification>> registry = new HashMap<>();

    static {
        // Register default types
        register("email", EmailNotification::new);
        register("sms", SMSNotification::new);
    }

    public static void register(String type, Supplier<Notification> creator) {
        registry.put(type.toLowerCase(), creator);
    }

    public static Notification create(String type) {
        Supplier<Notification> creator = registry.get(type.toLowerCase());
        if (creator == null) {
            throw new IllegalArgumentException("Unknown notification type: " + type);
        }
        return creator.get();
    }
}

When Should You Use It?

  • When object creation is complex or involves conditional logic.
  • When you want to decouple creation from usage.
  • When you might add new types in future, and don’t want to break existing logic.
  • When instantiation logic might change over time.

Where You’ve Already Seen It

Calendar.getInstance() in Java

LoggerFactory.getLogger() in logging frameworks

ConnectionFactory in DB access layers

Spring beans (kind of like factories behind the scenes)