Factory Method Pattern in Java – Payment Modes Example
By Ravi Shankar · officialcto.com
Introduction
When building applications, we often need to create objects based on runtime input.
For example, in a payment gateway system, users might choose to pay with Credit Card, UPI, or PayPal.
If we simply scatter new CreditCardPayment()
and new UpiPayment()
everywhere in our code, it becomes hard to maintain, extend, and test.
The Factory Method pattern provides a clean way to handle this problem by centralizing object creation.
The Problem: Without a Factory
Imagine this naive approach:
public class Main {
public static void main(String[] args) {
// Direct instantiation
Payment payment = new CreditCardPayment();
payment.pay(1000);
Payment upi = new UpiPayment();
upi.pay(500);
}
}
Looks fine at first. But what if tomorrow we add:
- PayPalPayment
- CryptoPayment
- NetBankingPayment
We’d need to change the client code in multiple places.
That’s a violation of the Open/Closed Principle (OCP) — code should be open for extension, closed for modification.
Enter the Factory Method Pattern
Instead of the client directly instantiating concrete classes, we introduce a factory that decides which Payment
object to return based on input.
Code Walkthrough
1. The Product (Abstract Class)
abstract class Payment {
public abstract void pay(int amount);
}
All payment modes extend this abstract class and implement their own logic.
2. Concrete Products
public class CreditCardPayment extends Payment {
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using Credit Card.");
}
}
public class UpiPayment extends Payment {
@Override
public void pay(int amount) {
System.out.println("Paid " + amount + " using UPI.");
}
}
3. The Factory
public class PaymentsFactory {
public static Payment createPayment(String type) {
if (type.equalsIgnoreCase("CREDIT_CARD")) {
return new CreditCardPayment();
} else if (type.equalsIgnoreCase("UPI")) {
return new UpiPayment();
}
throw new IllegalArgumentException("Unknown payment type: " + type);
}
}
👉 The factory method here is createPayment(String type)
.
It decides which concrete class to instantiate, based on input.
4. The Client
public class Main {
public static void main(String[] args) {
Payment payment1 = PaymentsFactory.createPayment("CREDIT_CARD");
payment1.pay(1000);
Payment payment2 = PaymentsFactory.createPayment("UPI");
payment2.pay(500);
}
}
Notice:
- The client never uses
new
. - It only depends on
Payment
(the abstract class). - Creation logic is isolated in
PaymentsFactory
.
UML Diagram
+----------------+
| Payment | <---- Abstract Product
+----------------+
| + pay(amount) |
+----------------+
^ ^
| |
+----------------+ +----------------+
| CreditCardPayment | | UpiPayment |
+----------------+ +----------------+
+---------------------+
| PaymentsFactory | <---- Factory
+---------------------+
| + createPayment() |
+---------------------+
Why This is Factory Method
- Client is decoupled from concrete classes.
- Factory encapsulates creation logic.
- Adding new payment modes only requires updating the factory (or registering new types).
Advantages
✅ Centralized object creation
✅ Client code is clean and depends only on abstractions
✅ Easy to add new payment types
✅ Improved testability — you can swap payment modes without touching client logic
Limitations
⚠️ The current implementation requires editing the factory every time you add a new payment mode (switch/if chain grows).
⚠️ Static factories are harder to extend compared to registry-based or DI-driven factories.
⚠️ It’s not the pure GoF Factory Method pattern (which uses subclassed creators) — this is a Simple Factory, often confused with Factory Method.
TODOs & Suggested Improvements
Use
enum
instead of raw stringsjavaenum PaymentType { CREDIT_CARD, UPI }
Registry-based Factory
Use aMap<String, Supplier<Payment>>
to avoidif-else
clutter.Abstract Factory
If you need families of related products (e.g.,Payment
,Refund
,Invoice
for each provider), use the Abstract Factory pattern instead.Dependency Injection
In real systems, payment objects often need configs, credentials, and clients. A DI framework (Spring) can manage that.
Conclusion
The Factory Method pattern (or more precisely, the Simple Factory approach we implemented) is a clean and practical solution for problems where object creation depends on runtime input.
Our payment modes example demonstrates how to keep client code clean, avoid hard-coded instantiation, and make the system easier to extend.
In real-world systems, factories often evolve into registry-based factories or get integrated with dependency injection frameworks for maximum flexibility.
✍️ Written by Ravi Shankar for officialcto.com