Factory Pattern: Stop Using if/else to Build Objects

Factory Pattern: Stop Using if/else to Build Objects

Creating objects with a chain of conditionals is one of those things that seems harmless at first. Then your codebase grows, and suddenly you're hunting down every if/else block whenever a new type is added.

The Factory Pattern eliminates that.


The Problem

if (type.equals("email")) {
    notification = new EmailNotification();
} 
else if (type.equals("sms")) {
    notification = new SmsNotification();
} 
else if (type.equals("push")) {
    notification = new PushNotification();
}
// Add "whatsapp"? Edit this block. And every other place it appears. 😬        

This violates the Open/Closed Principle — your code should be open to extension, not require modification every time a new type is added.


The Solution

Step 1 — Define the interface:

public interface Notification {
    void send(String message);
}        

Step 2 — Implement each type:

public class EmailNotification implements Notification {

    public void send(String message) {
        System.out.println("Email: " + message);
    }

}

public class SmsNotification implements Notification {

    public void send(String message) {
        System.out.println("SMS: " + message);
    }

}

public class PushNotification implements Notification {

    public void send(String message) {
        System.out.println("Push: " + message);
    }

}        

Step 3.1 — Build the Factory - Style 01:

public class NotificationFactory {

    public static Notification create(String type) {
        return switch (type.toLowerCase()) {
            case "email" -> new EmailNotification();
            case "sms"   -> new SmsNotification();
            case "push"  -> new PushNotification();
            default -> throw new IllegalArgumentException(
                           "Unknown type: " + type
            );
        };
    }
}        

Step 3.2 — Build the Factory - Style 02 (fancier - Java 21):

public static void dispatch(Notification n) {
    switch (n) {

        case EmailNotification e -> System.out.println(
                                       "Sending email to: " + e.getTo()
                                    );

        case SmsNotification s   -> System.out.println(
                                       "Sending SMS to: " + s.getPhone()
                                    );

        case PushNotification p  -> System.out.println(
                                        "Pushing to: " + p.getToken()
                                    );

        default -> throw new IllegalStateException(
                                         "Unknown notification type"
                   );
    }
}        
Better — but default is still required unless the compiler knows all possible types...

Step 4 — Clean client code:

Notification n = NotificationFactory.create("email"); 

n.send("Your order has been confirmed.");        

The client never calls new directly. It doesn't care which class it gets — only what it can do.


When to Use It

  • Object creation involves conditional logic
  • You want to add new types without touching existing client code
  • You need a single entry point for object creation


Key Takeaway

"The Factory Pattern hides how objects are created. Clients only know what those objects can do."

To view or add a comment, sign in

More articles by Paulo B.

Explore content categories