03 – Abstract Factory Pattern

03 – Abstract Factory Pattern

Abstract Factory Pattern in Java: Families of Related Objects

The Problem

Sometimes you need to create families of related objects that must stay consistent with each other — for example, UI components for different themes, or payment gateway objects for different regions. Abstract Factory provides an interface for creating families of related objects without specifying their concrete classes.

Diagram

%%{init: {'theme':'base', 'themeVariables': { 'primaryColor': '#eef2ff', 'primaryTextColor': '#1e293b', 'primaryBorderColor': '#818cf8', 'lineColor': '#94a3b8', 'secondaryColor': '#f0fdf4', 'secondaryBorderColor': '#86efac', 'tertiaryColor': '#fff7ed', 'tertiaryBorderColor': '#fdba74', 'background': '#ffffff', 'mainBkg': '#eef2ff', 'secondBkg': '#f0fdf4', 'tertiaryBkg': '#fff7ed', 'noteBkgColor': '#fff7ed', 'noteBorderColor': '#fdba74', 'actorBkg': '#eef2ff', 'actorBorder': '#818cf8' }}}%%
classDiagram
    class BillingComponentFactory {
        <<interface>>
        +createInvoiceGenerator() InvoiceGenerator
        +createPaymentProcessor() PaymentProcessor
    }
    class PrepaidFactory {
        +createInvoiceGenerator() InvoiceGenerator
        +createPaymentProcessor() PaymentProcessor
    }
    class PostpaidFactory {
        +createInvoiceGenerator() InvoiceGenerator
        +createPaymentProcessor() PaymentProcessor
    }
    class InvoiceGenerator {
        <<interface>>
    }
    class PaymentProcessor {
        <<interface>>
    }

    BillingComponentFactory <|.. PrepaidFactory
    BillingComponentFactory <|.. PostpaidFactory
    BillingComponentFactory ..> InvoiceGenerator
    BillingComponentFactory ..> PaymentProcessor

Java Example

public interface InvoiceGenerator { String generate(); }
public interface PaymentProcessor { void charge(double amount); }

public class PrepaidInvoiceGenerator implements InvoiceGenerator {
    public String generate() { return "Prepaid receipt"; }
}
public class PrepaidPaymentProcessor implements PaymentProcessor {
    public void charge(double amount) { System.out.println("Deducting balance: " + amount); }
}

public class PostpaidInvoiceGenerator implements InvoiceGenerator {
    public String generate() { return "Postpaid invoice"; }
}
public class PostpaidPaymentProcessor implements PaymentProcessor {
    public void charge(double amount) { System.out.println("Charging card: " + amount); }
}

public interface BillingComponentFactory {
    InvoiceGenerator createInvoiceGenerator();
    PaymentProcessor createPaymentProcessor();
}

public class PrepaidFactory implements BillingComponentFactory {
    public InvoiceGenerator createInvoiceGenerator() { return new PrepaidInvoiceGenerator(); }
    public PaymentProcessor createPaymentProcessor() { return new PrepaidPaymentProcessor(); }
}

public class PostpaidFactory implements BillingComponentFactory {
    public InvoiceGenerator createInvoiceGenerator() { return new PostpaidInvoiceGenerator(); }
    public PaymentProcessor createPaymentProcessor() { return new PostpaidPaymentProcessor(); }
}




Quarkus Example

CDI qualifiers make Abstract Factory very clean — each family is a set of beans tagged with a common qualifier, and a single injection point resolves the whole family:

@Qualifier
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER})
public @interface BillingMode {
    Mode value();
    enum Mode { PREPAID, POSTPAID }
}

@ApplicationScoped
@BillingMode(BillingMode.Mode.PREPAID)
public class PrepaidInvoiceGenerator implements InvoiceGenerator { /* ... */ }

@ApplicationScoped
@BillingMode(BillingMode.Mode.POSTPAID)
public class PostpaidInvoiceGenerator implements InvoiceGenerator { /* ... */ }

@ApplicationScoped
public class BillingResource {

    @Inject
    @BillingMode(BillingMode.Mode.PREPAID)
    InvoiceGenerator prepaidInvoiceGenerator;
}

Key Takeaways

  • Use Abstract Factory when families of objects must be created together and stay compatible (e.g., all prepaid components, all postpaid components).
  • It’s a step up from Factory Method: instead of one product, you produce a coherent set.
  • In Quarkus/CDI, custom qualifiers + @ApplicationScoped beans are the idiomatic way to implement product families — very useful in BSS/telco billing systems with prepaid/postpaid or per-tenant variants.