Factory Pattern explained simply
Factory Pattern
Imagine you want to create objects — but you don’t want to expose the creation logic to the client and instead ask a factory class to create objects for you.
That’s exactly what the Factory Pattern does.
What is the Factory Pattern?
At its core:
- Defines an interface for creating an object.
- Lets subclasses alter the type of objects that will be created.
- Centralizes object creation, making code more flexible and easier to maintain.
Class Diagram
Here is how the Factory Pattern looks when applied to a notification service. The NotificationFactory decides which concrete Notification to instantiate based on a channel string, so the client never has to deal with the concrete classes directly.
Real-Life Analogy
Think about a bakery:
- You place an order for a “cake”.
- You don’t worry about how it’s baked.
- The bakery (factory) prepares and gives you the cake.
You simply ask for an object and get it.
Same thing with notifications. Your application says “send this user a notification on their preferred channel” and the factory figures out whether that means firing off an email, an SMS, or a push notification. The calling code never needs to know the details.
Structure
In our notification example, here is how each piece maps to the pattern:
- Product (
Notification): The common interface. Every notification type knows how tosend(). - Concrete Products (
EmailNotification,SMSNotification,PushNotification): The actual implementations that handle the channel-specific logic. - Creator / Factory (
NotificationFactory): Has acreateNotification()method that returns the right concrete type based on the channel string you pass in.
The client code only ever talks to the Notification interface. It has no idea whether it is dealing with email, SMS, or push under the hood.
Java Example – Notification Service
Let’s build something you would actually use in a real project: a notification service where users pick their preferred channel.
First, define the Notification interface:
public interface Notification {
void send(String recipient, String message);
}
Now the concrete implementations. Each one handles the channel-specific details:
public class EmailNotification implements Notification {
@Override
public void send(String recipient, String message) {
System.out.println("Sending EMAIL to " + recipient + ": " + message);
// In a real app: configure SMTP, build MIME message, hit the mail server
}
}
public class SMSNotification implements Notification {
@Override
public void send(String recipient, String message) {
System.out.println("Sending SMS to " + recipient + ": " + message);
// In a real app: call Twilio API, handle rate limits, etc.
}
}
public class PushNotification implements Notification {
@Override
public void send(String recipient, String message) {
System.out.println("Sending PUSH to " + recipient + ": " + message);
// In a real app: hit Firebase Cloud Messaging or APNs
}
}
Create the NotificationFactory:
public class NotificationFactory {
public Notification createNotification(String channel) {
if (channel == null) {
throw new IllegalArgumentException("Notification channel cannot be null");
}
switch (channel.toLowerCase()) {
case "email":
return new EmailNotification();
case "sms":
return new SMSNotification();
case "push":
return new PushNotification();
default:
throw new IllegalArgumentException("Unknown channel: " + channel);
}
}
}
Using the Factory
Here is where it gets practical. Imagine a user preferences system where each user has chosen how they want to be notified. The client code stays clean regardless of how many channels you support:
public class NotificationService {
public static void main(String[] args) {
NotificationFactory factory = new NotificationFactory();
// Simulate user preferences loaded from a database
String[] userChannels = {"email", "sms", "push", "email"};
String[] recipients = {"[email protected]", "+94771234567", "device-token-xyz", "[email protected]"};
String alertMessage = "Your account login was detected from a new device.";
for (int i = 0; i < userChannels.length; i++) {
Notification notification = factory.createNotification(userChannels[i]);
notification.send(recipients[i], alertMessage);
}
}
}
Output:
Sending EMAIL to [email protected]: Your account login was detected from a new device.
Sending SMS to +94771234567: Your account login was detected from a new device.
Sending PUSH to device-token-xyz: Your account login was detected from a new device.
Sending EMAIL to [email protected]: Your account login was detected from a new device.
Notice how the loop does not care which notification type it is working with. It just asks the factory, gets back a Notification, and calls send(). If you add a WhatsAppNotification next month, the loop does not change at all – you only touch the factory and add the new class.
Why Use the Factory Pattern?
- Encapsulates object creation: All the “which class do I instantiate?” logic lives in one place. When you add a new notification channel, you update the factory and nothing else.
- Decouples code: Your notification service depends on the
Notificationinterface, not onEmailNotificationorSMSNotificationdirectly. - Easier maintenance and scalability: Adding a new channel means writing one new class and one new case in the factory. Zero changes to the client code.
Real-World Use Cases
- Notification / Messaging Systems: Exactly what we built above – routing messages to different channels based on user preferences or event types.
- Payment Gateways: A
PaymentProcessorFactorythat returns a Stripe, PayPal, or Square processor depending on the merchant’s configuration. - Java Standard Library:
Calendar.getInstance(),NumberFormat.getInstance(),DriverManager.getConnection()– all factory methods hiding the concrete implementation from you. - Logging Frameworks: SLF4J’s
LoggerFactory.getLogger()returns the right logger implementation without you caring whether it is Logback, Log4j, or something else. - Cloud SDKs: Creating service clients for different cloud providers through a unified factory interface.
Factory Pattern vs. Abstract Factory Pattern
- Factory Pattern: Creates one product type (e.g., different kinds of
Notification). - Abstract Factory Pattern: Creates families of related products (e.g., a
UIFactorythat produces buttons, text fields, and dropdowns that all share the same look and feel).
Summary
The Factory Pattern helps you delegate object creation to a separate method or class. In our notification example, the calling code just says “give me a notification for this channel” and the factory handles the rest.
It hides the instantiation details and allows your code to depend on interfaces rather than concrete classes – making it more flexible, robust, and easier to extend. Next time you find yourself writing a bunch of if/else blocks to decide which class to instantiate, that is your cue to reach for a factory.