Definition
- Factory pattern – a creational pattern that centralises object creation behind a dedicated factory so callers request a product by key instead of using
new. - Three parts – a product interface (common contract), concrete product classes (implementations), and a factory (decides which concrete product to create).
- Decoupling – client code depends only on the interface and the factory, not on concrete classes.
- OCP – adding a new product means adding a class and one factory entry; existing code is untouched.
- Modern form – a
Mapor Spring's> Mapbean injection.
At the National Bank of India there is one account-opening counter. Rajesh walks up and says "I want a savings account" or "I want a current account," and the clerk hands him the correct, fully prepared kit. Rajesh never goes into the back room to assemble the kit himself. The counter is the factory, the kit types are the concrete products, and the general idea of an account kit is the product interface.
Understanding the Concepts
What is the Factory pattern?
The Factory pattern has three parts: a product interface, concrete products, and a factory that maps an input to the right product.
package com.bank.service;
// Product interface
public interface NotificationChannel {
void send(String to, String message);
}
// Concrete products
public class SmsChannel implements NotificationChannel {
public void send(String to, String message) {
System.out.println("SMS to " + to + ": " + message);
}
}
public class EmailChannel implements NotificationChannel {
public void send(String to, String message) {
System.out.println("Email to " + to + ": " + message);
}
}
public class PushChannel implements NotificationChannel {
public void send(String to, String message) {
System.out.println("Push to " + to + ": " + message);
}
}
// Factory
public class ChannelFactory {
public static NotificationChannel create(String type) {
return switch (type) {
case "SMS" -> new SmsChannel();
case "EMAIL" -> new EmailChannel();
case "PUSH" -> new PushChannel();
default -> throw new IllegalArgumentException("Unknown channel: " + type);
};
}
}
The caller writes ChannelFactory.create("SMS") and then channel.send(...), never knowing the concrete class.
What are the benefits of Factory?
- Decoupling: client code depends only on the interface and the factory, not concrete classes.
- Single point of change: creation logic lives in one method; adding a cache or parameter touches one place.
- OCP: adding a channel means adding a class and one factory branch. Existing code is untouched.
- Polymorphism: the factory returns the interface type, enabling clean polymorphic dispatch.
package com.bank.service;
public class AlertService {
// Depends only on NotificationChannel interface
public void sendAlert(String channel, String to, String message) {
NotificationChannel ch = ChannelFactory.create(channel);
ch.send(to, message);
}
}
What are common use cases?
- Choosing an implementation by configuration: pick an SMS provider per region.
- Abstracting creation in frameworks: Spring's bean container is a factory.
- Hiding complex or conditional construction: building a parser based on file extension.
package com.bank.demo;
public class FactoryUseCaseDemo {
public static void main(String[] args) {
// Customer preference determines the channel
String preference = "SMS"; // from database
NotificationChannel channel = ChannelFactory.create(preference);
channel.send("+91-9876543210", "Your transfer of 50,000 INR is complete.");
}
}
SMS to +91-9876543210: Your transfer of 50,000 INR is complete.
How does Factory look in modern Java?
Replace the switch with a Map:
package com.bank.service;
import java.util.Map;
import java.util.function.Supplier;
public class ModernChannelFactory {
private static final Map<String, Supplier<NotificationChannel>> REGISTRY = Map.of(
"SMS", SmsChannel::new,
"EMAIL", EmailChannel::new,
"PUSH", PushChannel::new
);
public static NotificationChannel create(String type) {
Supplier<NotificationChannel> supplier = REGISTRY.get(type);
if (supplier == null) {
throw new IllegalArgumentException("Unknown: " + type);
}
return supplier.get();
}
}
The map is trivially extensible: adding a channel is adding one entry.
How does Factory work in Spring?
In Spring, injecting a Map gives a factory for free:
package com.bank.service;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class SpringAlertService {
private final Map<String, NotificationChannel> channels;
public SpringAlertService(Map<String, NotificationChannel> channels) {
this.channels = channels;
}
public void send(String channelName, String to, String message) {
NotificationChannel ch = channels.get(channelName);
if (ch == null) throw new IllegalArgumentException("Unknown: " + channelName);
ch.send(to, message);
}
}
Adding a new channel is adding a new @Component bean. No factory edit.
What are common mistakes with Factory?
- Having the factory return concrete types instead of the interface, defeating the decoupling.
- Over-using Factory for objects that have no variation: a plain
newis simpler. - Putting business logic inside the factory instead of in the products.
How does Factory support testing?
Because the factory returns the interface, tests can create a mock or stub:
// In tests
NotificationChannel mockChannel = (to, msg) -> System.out.println("MOCK: " + msg);
// inject mockChannel instead of calling the factory
What should a senior engineer know?
- The switch-based factory is fine for a handful of types; the Map-based factory scales.
- In Spring, the container itself is a factory. Understanding this makes the pattern feel natural.
- Factory Method (GoF) specifically uses a method that subclasses override; the term "factory" in daily use is broader.
- At National Bank of India, the alert factory was the fix for scattered
new SmsChannel()calls that caused missed alerts when push notifications were added.
Visual Overview
Factory Pattern Structure
─────────────────────────
Client Factory
────── ───────
AlertService ──▶ ChannelFactory.create("SMS")
│ │
│ ▼
│ ┌──────────────────┐
│ │ NotificationChannel│ ◄── interface
│ └────────┬─────────┘
│ │
│ ┌──────────┼──────────┐
│ ▼ ▼ ▼
│ SmsChannel EmailChannel PushChannel
│
▼
channel.send(to, msg) ◄── works with any channel
Key Takeaway
The Factory pattern centralises object creation behind one place, so calling code asks for a product by type instead of using new directly. It has three parts: a product interface that defines the common contract, concrete product classes that implement it, and a factory that decides which one to create.
The benefits are decoupling, a single point of change, OCP compliance, and clean polymorphism. At National Bank of India, Rajesh had alert-sending code scattered with new SmsChannel() in many places. When push notifications were added, some code paths were missed and customers never got alerts. He fixed it with a ChannelFactory, so every alert goes through one factory. Adding WhatsApp was a new class plus one registry entry, with every existing alert untouched.
In modern Java, the factory switch is replaced by a Map, and in Spring, injecting a Map gives a factory for free. Adding a new channel is literally adding a new @Component bean.
The senior judgment is to use Factory when there is real variation in which concrete type to create, and to keep a plain new when there is no variation. Over-applying Factory adds indirection for no benefit.
Remember one sentence: Factory hides the new keyword behind one place, so callers depend only on the interface and adding a product is adding a class, not editing callers.
---