Abstract Factory is a creational design pattern, allowing the user to create families of related or dependent objects without specifying their concrete classes. It belongs to the first group of patterns that deal with object creation and promote loose coupling by separating the client code from the implementation details of the object creation process.

Structure

The Abstract Factory pattern consists of the following key components:

  • Abstract Factory: is an interface that declares methods to create abstract products
  • Concrete Factory: implements the creation methods in the Abstract Factory interface, each variant corresponds to a specific group of products
  • Abstract Product: is an interface for each distinct type of product
  • Concrete Product: implements the Abstract Product interface,
  • Client: uses objects returned by the Abstract Factory

When to use Abstract Factory Pattern?

  • When you want to abstract the object-creation logic in a separate class (Single Responsibility Principle)
  • When you want to create objects of one family of products with one call
  • When you want to add a new type of family to an application in the future without making too many changes (Open/Closed Principle)
  • When you want to create objects using factories, and not using client code, leading to easier code maintenance, testing, and extension

Implementation Example

This code demonstrates the implementation of the Abstract Factory design pattern in Java. The code is structured to create different combos of sandwiches, drinks, and sides for a fast-food restaurant.

The Abstract Factory pattern allows the client code to create families of related objects (sandwiches, drinks, and sides) without directly depending on the concrete classes. It provides a way to encapsulate the creation of objects and ensures that the created objects are compatible.

In this example, the client code can easily create different combos by using different concrete factories, and it can be extended to support new combos or products without modifying the existing client code.

import java.util.*;

// client class
public class Solution {

    public static void main(String[] args) {

        AbstractComboFactory comboOne = new ComboOneFactory();
        AbstractSandwich sandwichOne = comboOne.createSandwich();
        AbstractDrink drinkOne = comboOne.createDrink();
        AbstractSide sideOne = comboOne.createSide();
        System.out.println("Combo: " + sandwichOne.getCombo());
        System.out.println("Sandwich: " + sandwichOne.getName());
        System.out.println("Drink: " + drinkOne.getName());
        System.out.println("Side: " + sideOne.getName());

        System.out.println("-------------------------------------------------------------------------------------");

        AbstractComboFactory comboTwo = new ComboTwoFactory();
        AbstractSandwich sandwichTwo = comboTwo.createSandwich();
        AbstractDrink drinkTwo = comboTwo.createDrink();
        AbstractSide sideTwo = comboTwo.createSide();
        System.out.println("Combo: " + sandwichTwo.getCombo());
        System.out.println("Sandwich: " + sandwichOne.getName());
        System.out.println("Drink: " + drinkTwo.getName());
        System.out.println("Side: " + sideTwo.getName());
    }
}

// interface for abstract factory
interface AbstractComboFactory {

    public abstract AbstractSandwich createSandwich();

    public abstract AbstractDrink createDrink();

    public abstract AbstractSide createSide();
}

// concrete product classes
class ComboOneFactory implements AbstractComboFactory {

    public AbstractSandwich createSandwich() {

        return new SpicyChickenSandwich();
    }

    public AbstractDrink createDrink() {

        return new CocaCola();
    }

    public AbstractSide createSide() {

        return new SpicyFries();
    }
}

class ComboTwoFactory implements AbstractComboFactory {

    public AbstractSandwich createSandwich() {

        return new VegSandwich();
    }

    public AbstractDrink createDrink() {

        return new Pepsi();
    }

    public AbstractSide createSide() {

        return new NormalFries();
    }
}

// interfaces for different product types
interface AbstractSandwich {

    public abstract String getCombo();

    public abstract String getName();
}

interface AbstractDrink {

    public abstract String getName();
}

interface AbstractSide {

    public abstract String getName();
}

// concrete product classes
class SpicyChickenSandwich implements AbstractSandwich {

    public String getCombo() {
        
        return "You get a spicy Chicken Sandwich, Coca Cola, and Spicy Fries with this combo!";
    }

    public String getName() {
        
        return "Spicy Chicken Sandwich!";
    }
}

class CocaCola implements AbstractDrink {

    public String getName() {
        
        return "Coca Cola!";
    }
}

class SpicyFries implements AbstractSide {

    public String getName() {
        
        return "Spicy Fries!";
    }
}

class VegSandwich implements AbstractSandwich {

    public String getCombo() {
        
        return "You get a spicy Veg Sandwich, Pepsi, and Normal Fries with this combo!";
    }

    public String getName() {
        
        return "Veg Sandwich!";
    }
}

class Pepsi implements AbstractDrink {

    public String getName() {
        
        return "Pepsi!";
    }
}

class NormalFries implements AbstractSide {

    public String getName() {
        
        return "Normal Fries!";
    }
}

The output of the following program will be:

Combo: You get a spicy Chicken Sandwich, Coca Cola, and Spicy Fries with this combo!
Sandwich: Spicy Chicken Sandwich!
Drink: Coca Cola!
Side: Spicy Fries!
-------------------------------------------------------------------------------------
Combo: You get a spicy Veg Sandwich, Pepsi, and Normal Fries with this combo!
Sandwich: Spicy Chicken Sandwich!
Drink: Pepsi!
Side: Normal Fries!