✅ What is Factory Design Pattern?¶
🔹 Definition¶
Factory Pattern is a creational design pattern used to:
Create objects without exposing the object creation logic to the client.
👉 Instead of using new, the client calls a factory method.
Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
🎯 Best Way to Explain in Interview¶
"Factory pattern provides a way to create objects without exposing instantiation logic, promoting loose coupling. The client interacts with a factory instead of directly creating objects using constructors.
✅ Why Use Factory Pattern?¶
- Encapsulates object creation logic
- Promotes loose coupling
- Makes code scalable & maintainable
- Easy to add new types without changing client code
✅ Real-life Example : Payment Gateway¶
This is a realistic example where an e-commerce application supports multiple payment methods:
- UPI
- Credit Card
- Stripe
- Net Banking
The client only asks the factory for a payment method and does not know the concrete implementation.
1. PaymentType.java¶
public enum PaymentType {
UPI,
CREDIT_CARD,
STRIPE,
NET_BANKING
}
2. PaymentRequest.java¶
Marker interface for all payment requests.
Whether
PaymentRequestis truly a marker interface depends on its intent: - If it's only used to mark classes → Marker Interface - If it's used as a common type for polymorphism → Base/Tagging Interface (more common in application code)
public interface PaymentRequest {
}
3. Payment request Implementations¶
UpiPaymentRequest.java¶
public class UpiPaymentRequest implements PaymentRequest {
private final String upiId;
public UpiPaymentRequest(String upiId) {
this.upiId = upiId;
}
public String getUpiId() {
return upiId;
}
}
CreditCardPaymentRequest.java¶
public class CreditCardPaymentRequest
implements PaymentRequest {
private final String cardNumber;
private final String cardHolderName;
private final String expiryDate;
private final String cvv;
public CreditCardPaymentRequest(
String cardNumber,
String cardHolderName,
String expiryDate,
String cvv) {
this.cardNumber = cardNumber;
this.cardHolderName = cardHolderName;
this.expiryDate = expiryDate;
this.cvv = cvv;
}
public String getCardNumber() {
return cardNumber;
}
public String getCardHolderName() {
return cardHolderName;
}
public String getExpiryDate() {
return expiryDate;
}
public String getCvv() {
return cvv;
}
}
StripePaymentRequest.java¶
public class StripePaymentRequest
implements PaymentRequest {
private final String token;
public StripePaymentRequest(String token) {
this.token = token;
}
public String getToken() {
return token;
}
}
NetBankingPaymentRequest.java¶
public class NetBankingPaymentRequest
implements PaymentRequest {
private final String bankName;
private final String accountNumber;
public NetBankingPaymentRequest(
String bankName,
String accountNumber) {
this.bankName = bankName;
this.accountNumber = accountNumber;
}
public String getBankName() {
return bankName;
}
public String getAccountNumber() {
return accountNumber;
}
}
4. PaymentGateway.java¶
public interface PaymentGateway {
void pay(
double amount,
PaymentRequest request);
}
5. Conrete implementation¶
UpiPayment.java¶
public class UpiPayment
implements PaymentGateway {
@Override
public void pay(
double amount,
PaymentRequest request) {
UpiPaymentRequest upiRequest =
(UpiPaymentRequest) request;
System.out.println("\n===== UPI PAYMENT =====");
System.out.println(
"UPI ID: "
+ upiRequest.getUpiId());
System.out.println(
"Amount: ₹" + amount);
System.out.println(
"Payment Successful");
}
}
CreditCardPayment.java¶
public class CreditCardPayment
implements PaymentGateway {
@Override
public void pay(
double amount,
PaymentRequest request) {
CreditCardPaymentRequest cardRequest =
(CreditCardPaymentRequest) request;
System.out.println(
"\n===== CREDIT CARD PAYMENT =====");
System.out.println(
"Card Holder: "
+ cardRequest.getCardHolderName());
System.out.println(
"Card Number: "
+ cardRequest.getCardNumber());
System.out.println(
"Expiry: "
+ cardRequest.getExpiryDate());
System.out.println(
"Amount: ₹" + amount);
System.out.println(
"Payment Successful");
}
}
StripePayment.java¶
public class StripePayment
implements PaymentGateway {
@Override
public void pay(
double amount,
PaymentRequest request) {
StripePaymentRequest stripeRequest =
(StripePaymentRequest) request;
System.out.println(
"\n===== STRIPE PAYMENT =====");
System.out.println(
"Stripe Token: "
+ stripeRequest.getToken());
System.out.println(
"Amount: ₹" + amount);
System.out.println(
"Calling Stripe API...");
System.out.println(
"Payment Successful");
}
}
NetBankingPayment.java¶
public class NetBankingPayment
implements PaymentGateway {
@Override
public void pay(
double amount,
PaymentRequest request) {
NetBankingPaymentRequest netBankingRequest =
(NetBankingPaymentRequest) request;
System.out.println(
"\n===== NET BANKING PAYMENT =====");
System.out.println(
"Bank: "
+ netBankingRequest.getBankName());
System.out.println(
"Account Number: "
+ netBankingRequest.getAccountNumber());
System.out.println(
"Amount: ₹" + amount);
System.out.println(
"Payment Successful");
}
}
5. PaymentFactory.java¶
public class PaymentFactory {
public static PaymentGateway getPaymentGateway(
PaymentType paymentType) {
return switch (paymentType) {
case UPI ->
new UpiPayment();
case CREDIT_CARD ->
new CreditCardPayment();
case STRIPE ->
new StripePayment();
case NET_BANKING ->
new NetBankingPayment();
};
}
}
6. CheckoutService.java¶
public class CheckoutService {
public void checkout(
double amount,
PaymentType paymentType,
PaymentRequest request) {
PaymentGateway paymentGateway =
PaymentFactory
.getPaymentGateway(paymentType);
paymentGateway.pay(
amount,
request);
}
}
7. Main.java¶
public class Main {
public static void main(String[] args) {
CheckoutService checkoutService =
new CheckoutService();
// UPI Payment
checkoutService.checkout(
5000,
PaymentType.UPI,
new UpiPaymentRequest(
"john@okhdfcbank")
);
// Credit Card Payment
checkoutService.checkout(
10000,
PaymentType.CREDIT_CARD,
new CreditCardPaymentRequest(
"4111111111111111",
"John Doe",
"12/30",
"123")
);
// Stripe Payment
checkoutService.checkout(
15000,
PaymentType.STRIPE,
new StripePaymentRequest(
"stripe_token_123")
);
// Net Banking Payment
checkoutService.checkout(
20000,
PaymentType.NET_BANKING,
new NetBankingPaymentRequest(
"HDFC Bank",
"1234567890")
);
}
}
Output¶
===== UPI PAYMENT =====
UPI ID: john@okhdfcbank
Amount: ₹5000.0
Payment Successful
===== CREDIT CARD PAYMENT =====
Card Holder: John Doe
Card Number: 4111111111111111
Expiry: 12/30
Amount: ₹10000.0
Payment Successful
===== STRIPE PAYMENT =====
Stripe Token: stripe_token_123
Amount: ₹15000.0
Calling Stripe API...
Payment Successful
===== NET BANKING PAYMENT =====
Bank: HDFC Bank
Account Number: 1234567890
Amount: ₹20000.0
Payment Successful
Real Production Example¶
Usually each payment gateway requires different logic.
public class StripePayment implements PaymentGateway {
@Override
public void pay(double amount) {
// Create payment intent
// Call Stripe API
// Verify response
// Update transaction status
System.out.println(
"Calling Stripe API for amount ₹" + amount
);
}
}
public class UpiPayment implements PaymentGateway {
@Override
public void pay(double amount) {
// Generate UPI QR
// Create transaction request
// Poll payment status
System.out.println(
"Generating UPI request for ₹" + amount
);
}
}
The client never changes:
PaymentGateway gateway =
PaymentFactory.createPaymentGateway(paymentType);
gateway.pay(amount);
This is the key benefit of the Factory Pattern: new payment methods can be added with minimal changes to client code.
Interview-Level Improvement¶
Instead of a large switch, use a ma / registry-based factory:
public class PaymentFactory {
private static final Map<PaymentType,
Supplier<PaymentGateway>> registry =
Map.of(
PaymentType.UPI, UpiPayment::new,
PaymentType.CREDIT_CARD, CreditCardPayment::new,
PaymentType.STRIPE, StripePayment::new,
PaymentType.NET_BANKING, NetBankingPayment::new
);
public static PaymentGateway createPaymentGateway(
PaymentType type) {
return registry.get(type).get();
}
}
Benefits¶
- No huge switch statement.
- Easy to register new payment methods.
- Commonly used in Spring Boot and enterprise applications.
- Better follows the Open/Closed Principle.
This is closer to how factory-based object creation is implemented in large-scale backend systems.