- Published on
- · 10 min read
Abstract Factory Pattern in Java: Creating Families of Related Objects
- Authors
- Name
- Nguyễn Tạ Minh Trung
Table of Contents
Introduction to Abstract Factory Pattern
The Abstract Factory Pattern is one of the most important creational design patterns in software engineering. It provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes.
What is the Abstract Factory Pattern?
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It's like having a factory that produces different types of products, but all products from the same factory are designed to work together.
Key Characteristics:
- 🏭 Factory of Factories - Creates other factories
- 🔗 Related Objects - Products are designed to work together
- 🎭 Interface-based - Clients work with abstractions, not concrete classes
- 🔄 Interchangeable - Easy to switch between different product families
Components of Abstract Factory Pattern
1. Abstract Factory
Declares the interface for operations that create abstract products.
2. Concrete Factory
Implements the operations to create concrete product objects.
3. Abstract Product
Declares an interface for a type of product object.
4. Concrete Product
Defines a product object to be created by the corresponding concrete factory.
5. Client
Uses only interfaces declared by Abstract Factory and Abstract Product classes.
Abstract Factory UML Diagram

Real-World Use Cases
1. Cross-Platform GUI Applications
- Creating UI components (buttons, checkboxes, menus) for different operating systems
- Each OS has its own look and feel, but the same functionality
- Example: Windows, macOS, Linux GUI components
2. Database Connectivity
- Different database drivers (MySQL, PostgreSQL, Oracle)
- Each database has specific connection, statement, and result set implementations
- Client code remains database-agnostic
3. Game Development
- Different themes or environments (Medieval, SciFi, Fantasy)
- Each theme has its own set of characters, weapons, and environments
- Consistent visual style within each theme
4. Document Processing
- Different document formats (PDF, Word, HTML)
- Each format has specific parsers, formatters, and exporters
- Unified interface for document operations
5. E-commerce Platforms
- Different payment gateways (PayPal, Stripe, Square)
- Each gateway has specific payment, refund, and validation implementations
- Consistent payment processing interface
Programing in Java
Imagine a UI framework that supports multiple platforms: MacOS, Windows. Each platform has its own style of widgets like Button, Checkbox.
To keep the UI consistent, we should only use components from the same "family" (e.g., Mac-style Button with Mac-style Checkbox, not a Windows-style one). The Abstract Factory ensures we get the right components as a group.
Frameworks like Swing, SWT, or Android Themes follow similar abstract factory patterns for rendering views/components.
Programing UML Diagram

Step 1: Define Abstract Products
// Abstract Product A - Button
public interface Button {
void render();
void onClick();
}
// Abstract Product B - Checkbox
public interface Checkbox {
void render();
void toggle();
}
Step 2: Create Concrete Products for Windows
// Windows Button Implementation
public class WindowsButton implements Button {
@Override
public void render() {
System.out.println("Rendering windows button");
}
@Override
public void onClick() {
System.out.println("Clicked on windows button");
}
}
// Windows Checkbox Implementation
public class WindowsCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering windows checkbox");
}
@Override
public void toggle() {
System.out.println("Windows button toggled");
}
}
Step 3: Create Concrete Products for macOS
// macOS Button Implementation
public class MacButton implements Button {
@Override
public void render() {
System.out.println("Rendering Mac button");
}
@Override
public void onClick() {
System.out.println("Mac button clicked");
}
}
// macOS Checkbox Implementation
public class MacCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering Mac checkbox");
}
@Override
public void toggle() {
System.out.println("Mac button toggled");
}
}
Step 4: Define Abstract Factory
// Abstract Factory Interface
public interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
Step 5: Implement Concrete Factories
// Windows Factory Implementation
public class WindowsFactory extends GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
// macOS Factory Implementation
public class MacFactory extends GUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public Checkbox createCheckbox() {
return new MacCheckbox();
}
}
Step 6: Client Implementation
// Application class that uses the Abstract Factory
public class Application {
private final Button button;
private final Checkbox checkbox;
public Application(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void render() {
button.render();
checkbox.render();
}
public void onClick() {
button.onClick();
}
public void onToggle() {
checkbox.toggle();
}
}
Step 7: Demo Application
public class OSDetector {
public static String detectOS(String osName) {
if (osName.contains("win")) {
return "windows";
} else if (osName.contains("mac")) {
return "mac";
} else {
return "Unsupported OS";
}
}
}
public class GUIFactoryResolver {
public static GUIFactory getFactory(String osName) {
switch (osName) {
case "mac":
return new MacFactory();
case "windows":
return new WindowsFactory();
default:
throw new UnsupportedOperationException("Unsupported OS");
}
}
}
public class Main {
public static void main(String[] args) {
String osName = OSDetector.detectOS(System.getProperty("os.name").toLowerCase());
GUIFactory factory = GUIFactoryResolver.getFactory(osName);
Application app = new Application(factory);
app.render();
app.onClick();
app.onToggle();
}
}
Advanced Implementation Patterns
1. Factory Registry Pattern
public class FactoryRegistry {
private static final Map<String, Supplier<GUIFactory>> factories = new HashMap<>();
static {
registerFactory("windows", WindowsFactory::new);
registerFactory("macos", MacFactory::new);
}
public static void registerFactory(String type, Supplier<GUIFactory> factorySupplier) {
factories.put(type.toLowerCase(), factorySupplier);
}
public static GUIFactory getFactory(String type) {
Supplier<GUIFactory> factorySupplier = factories.get(type.toLowerCase());
if (factorySupplier == null) {
throw new IllegalArgumentException("No factory registered for type: " + type);
}
return factorySupplier.get();
}
public static Set<String> getSupportedTypes() {
return Collections.unmodifiableSet(factories.keySet());
}
}
Benefits and Advantages
✅ Advantages
1. Consistency
- Ensures that products from the same family work together
- Maintains consistent look and feel across the application
2. Flexibility
- Easy to switch between different product families
- New product families can be added without changing existing code
3. Separation of Concerns
- Client code is decoupled from concrete classes
- Product creation logic is centralized in factories
4. Single Responsibility Principle
- Each factory handles creation of one product family
- Clear separation between creation and business logic
5. Open/Closed Principle
- Open for extension (new factories and products)
- Closed for modification (existing code doesn't change)
❌ Disadvantages
1. Complexity
- Increases the number of classes and interfaces
- Can be overkill for simple applications
2. Rigid Structure
- Adding new product types requires changes to all factories
- Interface changes propagate to all implementations
3. Memory Overhead
- Multiple factory instances may consume more memory
- Caching strategies needed for better performance
When to Use Abstract Factory Pattern
✅ Use When:
Multiple Product Families
- System needs to work with multiple families of related products
- Products are designed to work together
Platform Independence
- Application should run on different platforms
- Each platform has specific implementations
Consistent Interface
- Need to enforce consistent interface across product families
- Want to ensure compatibility between related products
Runtime Decision
- Product family selection happens at runtime
- Configuration-driven object creation
❌ Avoid When:
Simple Applications
- Only one product family exists
- Unlikely to add new product families
Frequently Changing Products
- Product types change frequently
- Interface modifications are common
Performance Critical
- Object creation is performance-critical
- Overhead of abstraction is significant
Comparison with Other Patterns
Abstract Factory vs Factory Method
Aspect | Abstract Factory | Factory Method |
---|---|---|
Purpose | Creates families of related objects | Creates single objects |
Complexity | Higher complexity | Lower complexity |
Flexibility | Multiple product types | Single product type |
Use Case | Platform-specific components | Object creation with subclass control |
Abstract Factory vs Builder
Aspect | Abstract Factory | Builder |
---|---|---|
Focus | Family creation | Complex object construction |
Process | One-step creation | Multi-step construction |
Customization | Family-level | Object-level |
Immutability | Not enforced | Often creates immutable objects |
Abstract Factory vs Prototype
Aspect | Abstract Factory | Prototype |
---|---|---|
Creation Method | Constructor-based | Cloning-based |
Performance | May be slower | Faster for complex objects |
Initialization | From scratch | From existing instance |
Use Case | Different families | Similar objects with variations |
Common Pitfalls and Solutions
1. Over-Engineering
// ❌ Pitfall: Creating factory for simple cases
public class SimpleButtonFactory extends GUIFactory {
@Override
public Button createButton() {
return new SimpleButton(); // Only one implementation
}
// Unnecessary complexity for single implementation
}
// ✅ Solution: Use direct instantiation for simple cases
public class SimpleUI {
private Button button = new SimpleButton(); // Direct creation
}
2. Rigid Interface
// ❌ Pitfall: Interface that's hard to extend
public abstract class RigidFactory {
public abstract ComponentA createA();
public abstract ComponentB createB();
// Adding ComponentC requires changing all implementations
}
// ✅ Solution: Flexible interface design
public abstract class FlexibleFactory {
public abstract <T> T createComponent(Class<T> componentType);
// Or use a registry approach
private final Map<Class<?>, Supplier<?>> creators = new HashMap<>();
protected <T> void registerCreator(Class<T> type, Supplier<T> creator) {
creators.put(type, creator);
}
@SuppressWarnings("unchecked")
public <T> T create(Class<T> type) {
Supplier<T> creator = (Supplier<T>) creators.get(type);
if (creator == null) {
throw new IllegalArgumentException("No creator for type: " + type);
}
return creator.get();
}
}
3. Memory Leaks with Caching
// ❌ Pitfall: Unbounded cache
public class LeakyFactory {
private static final Map<String, GUIFactory> cache = new HashMap<>();
public static GUIFactory getFactory(String type) {
return cache.computeIfAbsent(type, k -> createFactory(k));
// Cache grows indefinitely!
}
}
// ✅ Solution: Bounded cache with eviction
public class BoundedCacheFactory {
private static final Map<String, GUIFactory> cache =
Collections.synchronizedMap(new LinkedHashMap<String, GUIFactory>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, GUIFactory> eldest) {
return size() > 10; // Limit cache size
}
});
public static GUIFactory getFactory(String type) {
return cache.computeIfAbsent(type, k -> createFactory(k));
}
}
Conclusion
The Abstract Factory pattern is a powerful creational design pattern that provides a robust solution for creating families of related objects. It excels in scenarios where:
Key Takeaways:
- ✅ Use Abstract Factory when you need to create families of related objects
- ✅ Ensures consistency across product families
- ✅ Provides flexibility to switch between implementations
- ✅ Promotes loose coupling between client and concrete classes
- ✅ Follows SOLID principles for maintainable code
The Abstract Factory pattern is particularly valuable in enterprise applications, cross-platform software, and any system that needs to support multiple implementations of related components. When implemented correctly, it provides a solid foundation for scalable and maintainable software architecture.