One-line Definition
Encapsulates object creation so clients depend on abstractions instead of concrete classes.
Problem
Suppose your application supports multiple payment methods such as Credit Card, UPI, and PayPal. If client code directly creates these classes using new, every new payment type requires changes throughout the application. The Factory Pattern moves object creation into a dedicated factory so client code depends only on the interface.
Real-world Analogy
You walk into a pizza shop and say "I'd like a pizza." The shop doesn't send you into the kitchen — it asks "Margherita? Pepperoni? Veggie?" and hands you the finished pizza. You never see how it's made. The factory (kitchen) handles all creation logic behind the scenes.
Structure
- Client
- Requests objects from the factory and works only with the Product interface.
- Factory
- Creates and returns the appropriate concrete product.
- Product
- Common interface shared by all products.
- Concrete Products
- Implement the Product interface.
Diagram
classDiagram
class Client {
}
class ShapeFactory {
+getShape(type: String): Shape
}
class Shape {
<<interface>>
+draw()
}
class Circle {
+draw()
}
class Square {
+draw()
}
Client --> ShapeFactory
Client --> Shape
ShapeFactory ..> Circle : creates
ShapeFactory ..> Square : creates
Circle ..|> Shape
Square ..|> Shape
Code Walkthrough
Notice that Main (the client) depends only on the Shape interface. The factory decides which implementation (Circle or Square) to create.
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing Circle");
}
}
class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing Square");
}
}
class ShapeFactory {
public Shape getShape(String shapeType) {
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
}
class Main {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
Shape shape1 = shapeFactory.getShape("CIRCLE");
shape1.draw();
Shape shape2 = shapeFactory.getShape("SQUARE");
shape2.draw();
}
}
Bad vs Good
Bad Approach
Problems
LogisticsServiceis tightly coupled to concrete classes.- Adding a new mode breaks the Open/Closed Principle.
- Hard to test independently.
interface Logistics {
void send();
}
class Road implements Logistics {
@Override
public void send() {
System.out.println("Sending by road logic");
}
}
class Air implements Logistics {
@Override
public void send() {
System.out.println("Sending by air logic");
}
}
class LogisticsService {
public void send(String mode) {
if (mode.equals("Air")) {
Logistics logistics = new Air();
logistics.send();
} else if (mode.equals("Road")) {
Logistics logistics = new Road();
logistics.send();
}
}
}
class Main {
public static void main(String[] args) {
LogisticsService service = new LogisticsService();
service.send("Air");
service.send("Road");
}
}
Better Approach
Improvements
- New modes are added by extending the factory, keeping business logic unchanged.
- Object creation is centralized.
interface Logistics {
void send();
}
class Road implements Logistics {
@Override
public void send() {
System.out.println("Sending by road logic");
}
}
class Air implements Logistics {
@Override
public void send() {
System.out.println("Sending by air logic");
}
}
class LogisticsFactory {
public static Logistics getLogistics(String mode) {
if (mode.equalsIgnoreCase("Air")) {
return new Air();
} else if (mode.equalsIgnoreCase("Road")) {
return new Road();
}
throw new IllegalArgumentException("Unknown logistics mode: " + mode);
}
}
class LogisticsService {
public void send(String mode) {
Logistics logistics = LogisticsFactory.getLogistics(mode);
logistics.send();
}
}
class Main {
public static void main(String[] args) {
LogisticsService service = new LogisticsService();
service.send("Air");
service.send("Road");
}
}
Pros vs Cons
| Pros | Cons |
|---|---|
| Decouples client code from concrete classes | More classes to maintain |
| Follows the Open/Closed Principle | Can be unnecessary for simple applications |
| Centralizes object creation | May introduce an extra abstraction layer |
| Runtime flexibility | Factory logic can grow complex if not refactored |
| Reduces duplicate instantiation logic | Obscures exact object types from the client |
When to Use
- Multiple implementations share a common interface.
- The concrete implementation is chosen at runtime.
- You want to reduce coupling between clients and implementations.
- Object creation involves more than a simple constructor call.
When Not to Use
- Only one implementation exists.
- Object creation is trivial.
- A constructor is sufficient.
- Adding a factory would only increase complexity.
Real-world Examples
java.util.Calendar.getInstance()java.util.ResourceBundle.getBundle()java.text.NumberFormat.getInstance()java.nio.charset.Charset.forName()- Spring Framework's
BeanFactory
Key Takeaway
The Factory Pattern separates object creation from object usage. Clients request an abstraction, while the factory decides which concrete implementation to return. Use it when implementations vary at runtime, but avoid introducing a factory when a simple constructor is enough.