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
Key Takeaway
"The Factory Pattern hides how objects are created. Clients only know what those objects can do."