Design Patterns: The Abstract Factory Pattern in Java
by Riley MacDonald, August 19, 2017

This is an extension of my previous post regarding the Factory Pattern in Java. The Abstract Factory pattern is generally used to create a family of dependent or related products while the Factory Pattern is used to create one type of Object. Both patterns consecrate object instantiation to subclasses. As I did in the previous post I’m using Sandwiches for this example. The ingredients of the sandwiches are limited to one to simplify the example (they use a single object for each abstract method). In reality sandwiches could contain more than one condiment.

Example Problem
This extends the example problem described in the previous post The Factory Pattern. Here we will extend the Sandwiches being created by applying the Abstract Factory Method.

Abstract Sandwich Factory
Let’s start with an abstract sandwich factory. This factory object defines the methods responsible for creating the basic elements of a sandwich. Each of the elements (Bread, Condiment, Topping and Meat are interfaces (with limited functionality for this example). Each method returns a different type defined via interface (see below).

public abstract class AbstractSandwichFactory {
    public abstract Bread createBread();
    public abstract Condiment createCondiments();
    public abstract Topping createToppings();
    public abstract Meat createMeat();
}

Sandwich Factory Implementations
Sandwich factory implementations define the ingredients used to create a sandwich.

public class ItalianSandwichFactory extends AbstractSandwichFactory {
    @Override
    public Bread createBread() {
        return new Roll();
    }
 
    @Override
    public Condiment createCondiments() {
        return new Mustard();
    }
 
    @Override
    public Topping createToppings() {
        return new Lettuce();
    }
 
    @Override
    public Meat createMeat() {
        return new Salami();
    }
}
 
public class MontrealSandwichFactory extends AbstractSandwichFactory {
    @Override
    public Bread createBread() {
        return new Rye();
    }
 
    @Override
    public Condiment createCondiments() {
        return new Mustard();
    }
 
    @Override
    public Topping createToppings() {
        return null; // just meat, no toppings!
    }
 
    @Override
    public Meat createMeat() {
        return new Smoked();
    }
}

Abstract Sandwich Object
This is type of object the sandwich stores will return.

public abstract class Sandwich {
    Bread bread;
    Condiment condiment;
    Topping topping;
    Meat meat;
 
    abstract void prepare();
 
    void pack() {
        System.out.println("Packing sandwich to go!");
    }
}

Sandwich Implementations
These are the instances that the subclasses (sandwich stores) will create when a sandwich is requested.

public class MontrealSandwich extends Sandwich {
    private AbstractSandwichFactory sandwichFactory;
 
    public MontrealSandwich(AbstractSandwichFactory sandwichFactory) {
        this.sandwichFactory = sandwichFactory;
    }
 
    @Override
    void prepare() {
        bread = sandwichFactory.createBread();
        condiment = sandwichFactory.createCondiments();
        meat = sandwichFactory.createMeat();
    }
}
 
public class ItalianSandwich extends Sandwich {
    AbstractSandwichFactory sandwichFactory;
 
    public ItalianSandwich(AbstractSandwichFactory sandwichFactory) {
        this.sandwichFactory = sandwichFactory;
    }
 
    @Override
    void prepare() {
        bread = sandwichFactory.createBread();
        condiment = sandwichFactory.createCondiments();
        topping = sandwichFactory.createToppings();
        meat = sandwichFactory.createMeat();
    }
}

Sandwich Stores
The client will write implementations of sandwich stores which are responsible for the instantiation of the sandwich objects (as we’ve already done in the previous Factory Pattern post). The sandwich stores own and instantiate the appropriate AbstractSandwichFactory. If an implementation of a sandwich doesn’t exist, the stores are able to provide their own implementation of AbstractSandwichFactory giving them complete control over how the system works within the constraints of the design.

public abstract class SandwichStore {
    public abstract Sandwich createSandwich(String type);
}
 
public class ItalianSandwichStore extends SandwichStore {
    private Sandwich sandwich = null;
    private AbstractSandwichFactory sandwichFactory = new ItalianSandwichFactory();
 
    @Override
    public Sandwich createSandwich(String type) {
        if (type.equals("italian")) {
            sandwich = new ItalianSandwich(sandwichFactory);
        }
 
        return sandwich;
    }
}
 
public class MontrealSandwichStore extends SandwichStore {
    private Sandwich sandwich = null;
    private final AbstractSandwichFactory sandwichFactory = new MontrealSandwichFactory();
 
    @Override
    public Sandwich createSandwich(String type) {
        if (type.equals("smoked")) {
            sandwich = new MontrealSandwich(sandwichFactory);
        }
 
        return sandwich;
    }
}

Using the Sandwich Factory as a client
The client is only responsible for creating the desired SandwichStores. The client can they request a sandwich using the createSanwich() method.

Summary
When new sandwiches are added or changes to existing sandwiches are made the logic pertaining to those sandwiches is encapsulated within their own classes. We’re programming against abstractions and interfaces instead of implementations. Maintenance and new features should be seamless following this design.

Open the comment form

Leave a comment:

Comments will be reviewed before they are posted.

User Comments:

Design Patterns: The Factory Pattern in Java | Riley MacDonald on 2017-08-19 13:23:33 said:
[…] this design pattern. It’s generally used for creating families of products. See my post on The Abstract Factory Method which extends this example and further customizes the creation of the […]