The Factory Method pattern defines an interface for creating objects but lets subclasses decide which class to instantiate. Instead of calling a constructor directly, client code delegates creation to a factory method, which returns an object that conforms to a common interface. This decouples the client from the concrete classes it uses.
Suppose your e-commerce platform needs to process payments through multiple providers — Stripe, PayPal, and Square. If your checkout code directly instantiates StripeProcessor or PayPalProcessor, adding a new provider means modifying the checkout logic. Every conditional branch increases complexity, and testing becomes harder because you cannot easily swap in a mock processor.
The Factory Method pattern introduces a creator class with a method that returns a PaymentProcessor interface. Each subclass (or a parameterized factory) decides which concrete processor to instantiate. The checkout code works only with the interface, so adding a new payment provider means adding a new class — no changes to existing code.
Program to an interface, not an implementation. The factory method encapsulates the “new” keyword behind a method call, so the client never depends on concrete classes. This is the Open/Closed Principle in action — open for extension, closed for modification.
This example creates payment processors for different providers through a factory method.
import java.math.BigDecimal;
// Common interface for all payment processors
public interface PaymentProcessor {
boolean charge(String customerId, BigDecimal amount);
boolean refund(String transactionId, BigDecimal amount);
String getProviderName();
}
// Concrete implementation: Stripe
public class StripeProcessor implements PaymentProcessor {
private final String apiKey;
public StripeProcessor(String apiKey) {
this.apiKey = apiKey;
}
@Override
public boolean charge(String customerId, BigDecimal amount) {
System.out.println("[Stripe] Charging $" + amount + " to customer " + customerId);
// Real Stripe SDK call would go here
return true;
}
@Override
public boolean refund(String transactionId, BigDecimal amount) {
System.out.println("[Stripe] Refunding $" + amount + " for txn " + transactionId);
return true;
}
@Override
public String getProviderName() { return "Stripe"; }
}
// Concrete implementation: PayPal
public class PayPalProcessor implements PaymentProcessor {
private final String clientId;
private final String clientSecret;
public PayPalProcessor(String clientId, String clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
}
@Override
public boolean charge(String customerId, BigDecimal amount) {
System.out.println("[PayPal] Charging $" + amount + " to customer " + customerId);
return true;
}
@Override
public boolean refund(String transactionId, BigDecimal amount) {
System.out.println("[PayPal] Refunding $" + amount + " for txn " + transactionId);
return true;
}
@Override
public String getProviderName() { return "PayPal"; }
}
// Concrete implementation: Square
public class SquareProcessor implements PaymentProcessor {
private final String accessToken;
public SquareProcessor(String accessToken) {
this.accessToken = accessToken;
}
@Override
public boolean charge(String customerId, BigDecimal amount) {
System.out.println("[Square] Charging $" + amount + " to customer " + customerId);
return true;
}
@Override
public boolean refund(String transactionId, BigDecimal amount) {
System.out.println("[Square] Refunding $" + amount + " for txn " + transactionId);
return true;
}
@Override
public String getProviderName() { return "Square"; }
}
// Factory that creates the right processor
public class PaymentProcessorFactory {
public static PaymentProcessor create(String provider) {
switch (provider.toLowerCase()) {
case "stripe":
return new StripeProcessor(System.getenv("STRIPE_API_KEY"));
case "paypal":
return new PayPalProcessor(
System.getenv("PAYPAL_CLIENT_ID"),
System.getenv("PAYPAL_SECRET")
);
case "square":
return new SquareProcessor(System.getenv("SQUARE_ACCESS_TOKEN"));
default:
throw new IllegalArgumentException("Unknown provider: " + provider);
}
}
}
// Usage
public class CheckoutService {
public static void main(String[] args) {
PaymentProcessor processor = PaymentProcessorFactory.create("stripe");
System.out.println("Using: " + processor.getProviderName());
processor.charge("cust_123", new BigDecimal("49.99"));
processor.refund("txn_456", new BigDecimal("10.00"));
}
}
The same payment processor factory in Python, using abstract base classes and a registry-based factory.
from abc import ABC, abstractmethod
from decimal import Decimal
import os
class PaymentProcessor(ABC):
@abstractmethod
def charge(self, customer_id: str, amount: Decimal) -> bool:
pass
@abstractmethod
def refund(self, transaction_id: str, amount: Decimal) -> bool:
pass
@abstractmethod
def provider_name(self) -> str:
pass
class StripeProcessor(PaymentProcessor):
def __init__(self, api_key: str):
self._api_key = api_key
def charge(self, customer_id: str, amount: Decimal) -> bool:
print(f"[Stripe] Charging ${amount} to customer {customer_id}")
return True
def refund(self, transaction_id: str, amount: Decimal) -> bool:
print(f"[Stripe] Refunding ${amount} for txn {transaction_id}")
return True
def provider_name(self) -> str:
return "Stripe"
class PayPalProcessor(PaymentProcessor):
def __init__(self, client_id: str, client_secret: str):
self._client_id = client_id
self._client_secret = client_secret
def charge(self, customer_id: str, amount: Decimal) -> bool:
print(f"[PayPal] Charging ${amount} to customer {customer_id}")
return True
def refund(self, transaction_id: str, amount: Decimal) -> bool:
print(f"[PayPal] Refunding ${amount} for txn {transaction_id}")
return True
def provider_name(self) -> str:
return "PayPal"
class SquareProcessor(PaymentProcessor):
def __init__(self, access_token: str):
self._access_token = access_token
def charge(self, customer_id: str, amount: Decimal) -> bool:
print(f"[Square] Charging ${amount} to customer {customer_id}")
return True
def refund(self, transaction_id: str, amount: Decimal) -> bool:
print(f"[Square] Refunding ${amount} for txn {transaction_id}")
return True
def provider_name(self) -> str:
return "Square"
class PaymentProcessorFactory:
_registry = {
"stripe": lambda: StripeProcessor(os.getenv("STRIPE_API_KEY", "")),
"paypal": lambda: PayPalProcessor(
os.getenv("PAYPAL_CLIENT_ID", ""),
os.getenv("PAYPAL_SECRET", ""),
),
"square": lambda: SquareProcessor(os.getenv("SQUARE_ACCESS_TOKEN", "")),
}
@classmethod
def create(cls, provider: str) -> PaymentProcessor:
creator = cls._registry.get(provider.lower())
if not creator:
raise ValueError(f"Unknown provider: {provider}")
return creator()
# Usage
if __name__ == "__main__":
processor = PaymentProcessorFactory.create("stripe")
print(f"Using: {processor.provider_name()}")
processor.charge("cust_123", Decimal("49.99"))
processor.refund("txn_456", Decimal("10.00"))
DriverManager.getConnection() returns a Connection implementation based on the JDBC URL, abstracting away MySQL, PostgreSQL, or Oracle specifics.json.loads() — the decoder factory can be swapped via the cls parameter to return custom object types.LoggerFactory.getLogger() returns a logger implementation from whichever logging backend is on the classpath (Logback, Log4j, etc.).