Factory Design Pattern

One common problem in software development is object creation. In small applications, creating objects using the new keyword is straightforward. However, as applications grow, object creation logic often becomes scattered across multiple classes and services.
When different parts of the system need to decide which implementation to create, the code becomes harder to maintain, test, and extend.
The Factory Design Pattern helps solve this problem by centralizing object creation logic and hiding implementation details from the client.
The Problem
Consider a credit card processing system that supports multiple card types:
Visa
MasterCard
American Express
RuPay
Without a factory, the application might contain code like this:
if(type == CreditCardType.VISA) {
card = new VisaCreditCard();
}
else if(type == CreditCardType.AMEX) {
card = new AmexCreditCard();
}
else if(type == CreditCardType.MASTERCARD) {
card = new MasterCardCreditCard();
}
else {
card = new RupayCreditCard();
}
Initially this may seem reasonable.
However, as the application grows, this logic often gets duplicated across multiple services:
PaymentService
BillingService
RewardService
CustomerService
Now every service knows:
Which concrete classes exist
How objects are created
Which implementation should be used
This creates tight coupling between business logic and object creation logic.
Why Is This a Problem?
Code Duplication
The same object creation logic appears in multiple places.
Tight Coupling
Client code becomes dependent on concrete implementations.
new VisaCreditCard()
new AmexCreditCard()
instead of depending on abstractions.
Harder Maintenance
Adding a new card type requires updating object creation logic everywhere it exists.
Difficult Testing
Directly creating concrete objects makes mocking and testing more difficult.
The Factory Solution:
Instead of creating objects directly, we delegate object creation to a factory.
The client asks the factory for an object.
CreditCard card =
CreditCardFactory.create(type);
The factory decides which implementation should be returned.
The client only interacts with the CreditCard interface.
Implementation
Step 1: Create a Common Interface
public interface CreditCard {
String getType();
String getExpiry();
}
This interface defines the behavior that all credit cards must provide.
Step 2: Create Concrete Implementations
Amex Credit Card
public class AmexCreditCard implements CreditCard {
@Override
public String getType() {
return "Amex";
}
@Override
public String getExpiry() {
return "2030-11-12";
}
}
Visa Credit Card
public class VisaCreditCard implements CreditCard {
@Override
public String getType() {
return "Visa";
}
@Override
public String getExpiry() {
return "2033-11-12";
}
}
Similarly, we can create:
MasterCardCreditCard
RupayCreditCard
All implementations follow the same contract.
Step 3: Create an Enum
public enum CreditCardType {
AMEX,
MASTERCARD,
VISA,
RUPAY
}
This prevents invalid card types from being passed into the factory.
Step 4: Create the Factory
public class CreditCardFactory {
public static CreditCard create(
CreditCardType type) {
return switch(type) {
case AMEX ->
new AmexCreditCard();
case MASTERCARD ->
new MasterCardCreditCard();
case VISA ->
new VisaCreditCard();
case RUPAY ->
new RupayCreditCard();
};
}
}
The factory contains all object creation logic in a single place.
Step 5: Client Usage
public class Main {
public static void main(String[] args) {
CreditCard card1 =
CreditCardFactory.create(
CreditCardType.AMEX);
CreditCard card2 =
CreditCardFactory.create(
CreditCardType.VISA);
System.out.println(
card1.getType());
System.out.println(
card2.getType());
}
}
Notice that the client never creates a concrete card directly.
The client only works with the abstraction.
How the Flow Works
Client
|
v
CreditCardFactory
|
+----> AmexCreditCard
|
+----> VisaCreditCard
|
+----> MasterCardCreditCard
|
+----> RupayCreditCard
The client does not need to know which implementation gets created.
That responsibility belongs entirely to the factory.
Benefits of Using Factory Pattern
Centralized Object Creation
All creation logic exists in one place.
CreditCardFactory
instead of being scattered throughout the application.
Reduced Coupling
The client depends on:
CreditCard
rather than:
VisaCreditCard
AmexCreditCard
MasterCardCreditCard
This makes the code more flexible.
Easier Maintenance
If object creation changes, only the factory needs to be updated.
Business logic remains untouched.
Better Testability
Clients can work against interfaces.
This makes mocking and dependency injection easier.
Limitations :
The Factory Pattern is not free.
It introduces an additional layer of abstraction.
For small applications:
new VisaCreditCard()
may be simpler than creating a factory.
Another common issue is large switch statements.
switch(type) {
...
}
As the number of implementations grows, the factory can become harder to maintain.
In such cases, more advanced patterns like Factory Method or Abstract Factory may be better choices.
Factory vs Factory Method vs Abstract Factory
These terms are often confused.
Simple Factory
Our credit card example is a Simple Factory.
CreditCardFactory.create(type)
A single class decides which object to create.
Factory Method
Object creation is delegated to subclasses.
abstract class CreditCardFactory {
abstract CreditCard createCard();
}
class VisaFactory
extends CreditCardFactory {
CreditCard createCard() {
return new VisaCreditCard();
}
}
Each factory subclass decides which product to create.
Abstract Factory
Abstract Factory creates families of related objects.
Example:
IndiaPaymentFactory
├── RupayCreditCard
└── RupayDebitCard
USPaymentFactory
├── VisaCreditCard
└── VisaDebitCard
Instead of creating one object, it creates multiple related objects.
When Should You Use Factory Pattern?
Use it when:
Multiple implementations exist
Object creation logic is duplicated
Client code should not know implementation details
Object creation becomes more complex over time
Examples include:
Payment Gateways
Notification Systems
Storage Providers
Database Drivers
Messaging Systems
Engineering Takeaways
The Factory Design Pattern is fundamentally about separating object creation from business logic.
Its primary goal is not to avoid the new keyword. The real value comes from reducing coupling and centralizing decisions about which implementation should be instantiated.
A useful rule of thumb is simple:
If multiple parts of the application are repeatedly deciding which implementation to create, that decision likely belongs inside a factory.
This keeps business logic focused on behavior while the factory remains responsible for construction.



