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.
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) { ... }
}
In the lambda era, we can replace complex switch statements with a Map of Suppliers. This is the "Modern Factory" pattern.
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
}
}
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 { ... }
Class.forName().newInstance() are slow and bypass compile-time checks. Prefer functional suppliers or ServiceLoaders.@Lookup or Provider<T> when you need a new instance inside a singleton.See Also: