Factory Method Pattern

August 4, 20195 min readUpdated 3/2/2026

Introduction

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.

The Problem

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 Solution

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.

Key Principle

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.

Java Example

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"));
    }
}

Python Example

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"))

When to Use

  • Multiple implementations of an interface — when your code needs to work with different concrete classes (payment providers, notification channels, file parsers) that share a common contract.
  • Decoupling creation from usage — when the client should not know or care which concrete class it receives, only that it satisfies the interface.
  • Plugin architectures — when third parties or configuration files determine which implementation to load at runtime.
  • Testing — when you need to swap real implementations for mocks or stubs without changing the code that consumes them.

Real-World Usage

  • JDBC DriverManagerDriverManager.getConnection() returns a Connection implementation based on the JDBC URL, abstracting away MySQL, PostgreSQL, or Oracle specifics.
  • Spring BeanFactory — creates beans by name or type, returning objects that implement requested interfaces.
  • Python’s json.loads() — the decoder factory can be swapped via the cls parameter to return custom object types.
  • SLF4J LoggerFactoryLoggerFactory.getLogger() returns a logger implementation from whichever logging backend is on the classpath (Logback, Log4j, etc.).