Factory Method Pattern

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



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *