DEV Community

Cover image for Simplifying Design Patterns : Abstract Factory
Bruno Caxias
Bruno Caxias

Posted on • Edited on

Simplifying Design Patterns : Abstract Factory

Introduction

Design patterns are proven solutions to common software design problems. In this article, we'll simplify the Abstract Factory pattern—explaining how it creates object families, when to use it, and how to implement it with kitchen-themed examples. Inspired by Refactoring Guru and Dive Into Design Patterns (Alexander Shvets).


How Does the Abstract Factory Work?

The Abstract Factory is a creational design pattern that produces families of related objects without specifying their concrete classes.

Key Characteristics

  • Creates multiple product types (e.g., Pizza + Pasta + Burger)
  • Ensures created objects are compatible (all Italian or all Mexican)
  • Uses composition over inheritance (factories are injected)

Example Scenario: Restaurant Kitchen

Imagine a kitchen that needs:

  • Multiple dish types (Pizzas, Pastas, Burgers)
  • Multiple cuisines (Italian, American, Mexican)

Instead of:

// ❌ Hardcoded kitchen (brittle)  
class Kitchen {  
    Pizza makeItalianPizza() {...}  
    Pasta makeAmericanPasta() {...} // Mixed cuisines!  
}  
Enter fullscreen mode Exit fullscreen mode

We use:

// ✅ Abstract Factory solution  
interface CuisineFactory {  
    Pizza createPizza();  
    Pasta createPasta();  
}  

class ItalianFactory implements CuisineFactory {  
    Pizza createPizza() { return new MargheritaPizza(); }  
    Pasta createPasta() { return new AlfredoPasta(); }  
}  
Enter fullscreen mode Exit fullscreen mode

When to Use Abstract Factory?

Use this pattern when:

  1. Working with object families – Your system needs groups of related products (e.g., GUI components, cuisine sets)
  2. Enforcing compatibility – Objects must work together (no "Italian Pizza + Mexican Pasta")
  3. Switching configurations – You need to change entire product families at runtime

How to Implement Abstract Factory

Step-by-Step (Restaurant Example)

  1. Define Product Interfaces
   interface Pizza { void bake(); }  
   interface Pasta { void boil(); }  
Enter fullscreen mode Exit fullscreen mode
  1. Create Concrete Products
   class MargheritaPizza implements Pizza {  
       @Override void bake() {  
           System.out.println("Baking thin-crust pizza");  
       }  
   }  
Enter fullscreen mode Exit fullscreen mode
  1. Declare Abstract Factory
   interface CuisineFactory {  
       Pizza createPizza();  
       Pasta createPasta();  
   }  
Enter fullscreen mode Exit fullscreen mode
  1. Implement Factories per Cuisine
   class MexicanFactory implements CuisineFactory {  
       @Override  
       Pizza createPizza() { return new TacoPizza(); }  

       @Override  
       Pasta createPasta() { return new ChipotlePasta(); }  
   }  
Enter fullscreen mode Exit fullscreen mode
  1. Use in Client Code
   class Kitchen {  
       private final CuisineFactory factory;  

       Kitchen(CuisineFactory factory) {  
           this.factory = factory;  
       }  

       void prepareMeal() {  
           Pizza p = factory.createPizza();  
           p.bake();  
       }  
   }  

   // Usage:  
   Kitchen italianKitchen = new Kitchen(new ItalianFactory());  
Enter fullscreen mode Exit fullscreen mode
  1. Extend with Variants (Optional)
   Pizza createPizza(PizzaStyle style) {  
       return switch(style) {  
           case NEAPOLITAN -> new NeapolitanPizza();  
           case SICILIAN -> new SicilianPizza();  
       };  
   }  
Enter fullscreen mode Exit fullscreen mode

Why Avoid Hardcoding Factories?

// ❌ Problematic approach  
class Kitchen {  
    Pizza makeItalianPizza() {...}  
    Pasta makeMexicanPasta() {...}  
    Burger makeAmericanBurger() {...}  
}  
Enter fullscreen mode Exit fullscreen mode

Issues:

  • Mixed cuisines create incompatible meals
  • Adding new cuisines requires modifying Kitchen
  • Violates Single Responsibility Principle

Diagram

Abstract Factory Structure
The client (Kitchen) works with factories and products only through interfaces.

Key Takeaways

Ensures compatibility – All objects belong to the same family

Simplifies swapping – Change entire themes by switching factories

Follows Open/Closed – Add new families without modifying code

Use Abstract Factory when you need coordinated object families with clean separation of concerns!

Top comments (0)