Factory Method Pattern
The Factory Method Pattern
The Factory Method pattern defines an interface for creating an object, but lets a parameter or subclass decide which class to instantiate. Instead of calling a constructor directly, your code calls a factory method that returns the right object. This decouples creation logic from business logic.
The Problem It Solves
Imagine a notification system. The type of notification (email, SMS, push) is known only at runtime from user preferences. Writing new EmailNotification() everywhere couples your code to one implementation. Adding a new type would force you to hunt down and change every call site.
+----------------+ create(type) +---------------------+
| Client code | ---------------> | NotificationFactory|
+----------------+ +---------------------+
| | |
+-------------+ | +--------------+
| | |
> > >
+-----------------+ +---------------+ +-----------------+
|EmailNotification| |SMSNotification| |PushNotification|
+-----------------+ +---------------+ +-----------------+
The client knows only Notification and NotificationFactory; the concrete class is chosen at runtime.
Ingredients
Java has no free functions, so a factory is usually a static method in a class. Start with the product interface and concrete implementations.
public interface Notification {
void send(String message);
}
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);
}
}
Each class implements the Notification contract, so callers depend only on the interface.
The Factory Method
The factory method accepts a type and returns the matching implementation.
public class NotificationFactory {
public static Notification create(String type) {
switch (type.toLowerCase()) {
case "email":
return new EmailNotification();
case "sms":
return new SMSNotification();
case "push":
return new PushNotification();
default:
throw new IllegalArgumentException("Unknown type: " + type);
}
}
}
// Usage - the service never instantiates a concrete class
public class NotificationService {
public void sendNotification(String type, String message) {
Notification notification = NotificationFactory.create(type);
notification.send(message);
}
}
Walkthrough: create() reads the type string, matches it in the switch, and returns the correct Notification. The service then sends through the interface. Adding a channel means one new class plus one new case in the factory - the service stays untouched, honoring the Open/Closed Principle.
Real-World Example: Logger
Logging frameworks select console, file, or database loggers through factories. Java itself uses Factory Methods in Calendar.getInstance() and DriverManager.getConnection().
public interface Logger {
void log(String level, String message);
}
public class ConsoleLogger implements Logger {
public void log(String level, String message) {
System.out.println("[" + level + "] " + message);
}
}
public class FileLogger implements Logger {
private final String filePath;
public FileLogger(String filePath) {
this.filePath = filePath;
}
public void log(String level, String message) {
// append to file
}
}
public class LoggerFactory {
public static Logger create(String type) {
switch (type) {
case "console":
return new ConsoleLogger();
case "file":
return new FileLogger("/var/log/app.log");
default:
throw new IllegalArgumentException("Unknown logger");
}
}
}
FileLogger also shows that factories can pass configuration arguments to constructors, hiding that complexity from the caller.
Key Points
- Factory Method defines an interface for creating objects but lets a type or subclass decide the concrete class.
- It decouples creation logic from business logic, making code easier to extend.
- The factory returns an interface type, never a concrete class.
- Adding a type means one new class and one new case - existing code stays untouched.
- Widely used in the Java standard library for connection and service creation.