Skip to content

Abstract Factory Pattern Guide

Creating whole families of related objects

Builds on Design Pattern: Simple Factory (Creational)

Stage 3core2 min read

The Abstract Factory Solution

The refactored code replaces hardcoded creation with a Factory-based design. The new RegionFactory interface separates the business logic from the regional classes.


1. Key Improvements

Family Consistency

  • The Benefit: A RegionFactory always gives you objects from one family (e.g., the India gateway and the India invoice).
  • The Safety: You cannot pair a US invoice with a Razorpay payment by accident, because IndiaFactory only creates Indian products.

Adherence to Open/Closed Principle (OCP)

  • The Benefit: To add a region (e.g., JapanFactory), write new classes that implement the existing interfaces.
  • The Impact: You never edit CheckoutService or the existing factories to support a new country.

Dependency Inversion

  • The Benefit: CheckoutService depends on abstractions (RegionFactory, PaymentGateway, Invoice), not on concrete classes (IndiaFactory, RazorpayGateway, etc.).
  • The Result: Changes in the low-level classes do not reach the high-level checkout logic.

2. Structural Breakdown

ComponentResponsibility
Abstract Factory (RegionFactory)The interface that lists the methods for creating each product.
Concrete Factory (IndiaFactory)Creates the real product objects for one region.
Abstract Product (PaymentGateway)The interface for one kind of product.
Concrete Product (PayPalGateway)A real product that its matching concrete factory creates.
Client (CheckoutService)Uses only the Abstract Factory and Abstract Product interfaces.

3. Comparison of "Before" vs "After"

FeatureBefore (Tight Coupling)After (Abstract Factory)
Object CreationInside CheckoutService using new.Delegated to a Factory object.
Adding a RegionModifying if/else in core service.Adding a new Factory class.
Product MatchingManual and error-prone.Automatic and guaranteed by the Factory.
Unit TestingHard (dependencies are hardcoded).Easy (pass in a "Mock Factory", a fake one).

In this topic

After code1 file

After/Main.java118 linesGitHub
package DesignPatterns.CreationalPatterns.AbstractFactoryDesignPattern.After;// 1. ABSTRACT PRODUCTS: Define what we need (Payment and Invoice)interface PaymentGateway {    void processPayment(double amount);}interface Invoice {    void generateInvoice();}// 2. CONCRETE PRODUCTS: Regional implementationsclass RazorpayGateway implements PaymentGateway {    public void processPayment(double amount) {        System.out.println("Processing INR payment via Razorpay: " + amount);    }}class GSTInvoice implements Invoice {    public void generateInvoice() {        System.out.println("Generating GST Invoice for India.");    }}class PayPalGateway implements PaymentGateway {    public void processPayment(double amount) {        System.out.println("Processing USD payment via PayPal: " + amount);    }}class USInvoice implements Invoice {    public void generateInvoice() {        System.out.println("Generating Invoice as per US norms.");    }}// 3. THE ABSTRACT FACTORY: An interface to create families of related objects.// It defines 'WHAT' to create, but not 'WHICH' concrete class to use.interface RegionFactory {    PaymentGateway createPaymentGateway(String gatewayType);    Invoice createInvoice();}// 4. CONCRETE FACTORIES: These handle regional specifics.// Note: These classes encapsulate the 'new' keyword and regional 'if-else' logic.class IndiaFactory implements RegionFactory {    public PaymentGateway createPaymentGateway(String gatewayType) {        if (            gatewayType.equalsIgnoreCase("razorpay")        ) return new RazorpayGateway();        // Add PayU logic here...        throw new IllegalArgumentException("Unsupported gateway for India.");    }    public Invoice createInvoice() {        return new GSTInvoice(); // Guaranteed consistency: India gets GST.    }}class USFactory implements RegionFactory {    public PaymentGateway createPaymentGateway(String gatewayType) {        if (gatewayType.equalsIgnoreCase("paypal")) return new PayPalGateway();        // Add Stripe logic here...        throw new IllegalArgumentException("Unsupported gateway for US.");    }    public Invoice createInvoice() {        return new USInvoice(); // Guaranteed consistency: US gets USInvoice.    }}/** * 5. THE CLIENT: Decoupled and flexible. * CheckoutService no longer cares about specific regional classes. * It only knows about the 'RegionFactory' interface. */class CheckoutService {    private final PaymentGateway paymentGateway;    private final Invoice invoice;    // Dependency Injection: The factory is passed in, not created inside.    public CheckoutService(RegionFactory factory, String gatewayType) {        this.paymentGateway = factory.createPaymentGateway(gatewayType);        this.invoice = factory.createInvoice();    }    public void completeOrder(double amount) {        paymentGateway.processPayment(amount);        invoice.generateInvoice();    }}class Main {    public static void main(String[] args) {        // We decide the "Family" (India) at the entry point of the application.        CheckoutService indiaCheckout = new CheckoutService(            new IndiaFactory(),            "razorpay"        );        indiaCheckout.completeOrder(1999.0);        // Switching regions is as easy as passing a different Factory instance.        CheckoutService usCheckout = new CheckoutService(            new USFactory(),            "paypal"        );        usCheckout.completeOrder(49.99);    }}

Done with this topic?

How well do you know it?

PreviousDesign Pattern: Simple Factory (Creational)