Creational Abstractions: The Factory Patterns

Factories decouple the usage of an object from its instantiation. This is critical for maintaining the Open/Closed Principle: you can add new implementations without modifying the client code that consumes them.

I. The Static Factory Method

Often preferred over constructors, static factory methods provide named intent and can return cached instances or subtypes.

Example: Named Intent

public class PaymentRequest {
    public static PaymentRequest forCreditCard(double amount) { ... }
    public static PaymentRequest forCrypto(double amount) { ... }
}

II. The Functional Factory (Modern Java)

In the lambda era, we can replace complex switch statements with a Map of Suppliers. This is the "Modern Factory" pattern.

Concrete Example: Dynamic Document Parser

public class ParserFactory {
    private static final Map<String, Supplier<Parser>> PARSERS = Map.of(
        "JSON", JsonParser::new,
        "XML", XmlParser::new,
        "CSV", CsvParser::new
    );

    public static Parser getParser(String format) {
        Supplier<Parser> constructor = PARSERS.get(format.toUpperCase());
        if (constructor == null) throw new IllegalArgumentException("Unknown format");
        return constructor.get(); // Lazy instantiation
    }
}

III. Abstract Factory: Families of Objects

Use Abstract Factory when you need to ensure that a set of related objects are created together consistently.

Example: Cloud Provider Abstraction

public interface CloudFactory {
    ComputeInstance createCompute();
    StorageBucket createBucket();
}

public class AwsFactory implements CloudFactory { ... }
public class AzureFactory implements CloudFactory { ... }

IV. Technical Considerations

  1. Reflection vs. New: Traditional factories using Class.forName().newInstance() are slow and bypass compile-time checks. Prefer functional suppliers or ServiceLoaders.
  2. DI Integration: In Spring/Guice, the container is the factory. Use @Lookup or Provider<T> when you need a new instance inside a singleton.
  3. Testability: Avoid global static state in factories. Inject the factory interface into the client so you can mock the object creation in unit tests.

See Also: