Abstract Factory Pattern

Abstract Factory Pattern definition

The Abstract Factory Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

It allows clients to use an abstract interface to create a set of related products without knowing (or caring) about the concrete products that are actually produced. With this, the client is decoupled from any of the specifics of the concrete products.

The abstract factory pattern is one of the classic Gang of Four creational design patterns used to create families of objects, where the objects of a family are designed to work together. In the abstract factory pattern you provide an interface to create families of related or dependent objects, but you do not specify the concrete classes of the objects to create. From the client point of view, it means that a client can create a family of related objects without knowing about the object definitions and their concrete class names.

Look at the example. Abstract Factory creates objects through composition. It creates an abstract type (PizzaIngredientFactory) for creating a family of products. Subclasses of this type (NYPizzaIngredientFactory, ChicagoPizzaIngredientFactory) define how those products are produced. To use the factory, you instantiate one (NYPizzaIngredientFactory) and pass it into some code (The passing is done from NYPizzaStore) that is written against the abstract type (NYPizzaIngredientFactory is passed into CheesePizza, VeggiePizza, PepperoniPizza). So the clients are decoupled from the actual concrete products they use.

Advantage: It groups together a set of related products (PizzaIngredientFactory groups together Dough, Sauce, Cheese, Veggies, Pepperoni, Clams). It needs a big interface because it creates entire families of products - where as, Factory Method pattern requires only one method (PizzaStore అనే abstract class లో createPizza అనే abstract method). Disadvantage: If you need to extend that set of related products (e.g. to add another one), it requires changing the interface.

When to use this pattern?

When you have families of products you need to create and you want to make sure your clients create products that belong together.

The key to the success of the PizzaStore is good, quality ingredients. With the framework that we have in place so far, the franchises are using the established “procedures” but some franchises have been substituting inferior ingredients.

How do we ensure consistency in the ingredients?

By creating factories for ingredients.

  1. We will build a factory to create our ingredients; this factory will be responsible for creating each ingredient in the ingredient family. We will need to handle regional differences.

Interface for the ingredient factory:

public interface PizzaIngredientFactory {
        public Dough createDough();
        public Sauce createSauce();
        public Cheese createCheese();
        public Veggies[] createVeggies();
        public Pepperoni createPepperoni();
        public Clams createClam();
}

We are introducing a lot of new classes here. One per ingredient. Dough, Sauce, Cheese, etc.

Steps after this:

  1. Build a factory for each region. Create a subclass of PizzaIngredientFactory that implements each create method.
  2. Implement a set of ingredient classes to be used with the factory. These classes can be shared among regions where appropriate.
  3. Hook all of this up by working the new ingredient factories into our old PizzaStore code.

NYPizzaIngredientFactory

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

        public Dough createDough() {
                return new ThinCrustDough();
        }

        public Sauce createSauce() {
                return new MarinaraSauce();
        }

        public Cheese createCheese() {
                return new ReggianoCheese();
        }

        public Veggies[] createVeggies() {
                Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
                return veggies;
        }

        public Pepperoni createPepperoni() {
                return new SlicedPepperoni();
        }

        public Clams createClam() {
                return new FreshClams();
        }
}

ChicagoPizzaIngredientFactory

public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory
{

        public Dough createDough() {
                return new ThickCrustDough();
        }

        public Sauce createSauce() {
                return new PlumTomatoSauce();
        }

        public Cheese createCheese() {
                return new MozzarellaCheese();
        }

        public Veggies[] createVeggies() {
                Veggies veggies[] = { new BlackOlives(),
                                      new Spinach(),
                                      new Eggplant() };
                return veggies;
        }

        public Pepperoni createPepperoni() {
                return new SlicedPepperoni();
        }

        public Clams createClam() {
                return new FrozenClams();
        }
}

Reworking the Pizza class:

public abstract class Pizza {
        String name;

        // Each pizza holds a set of ingredients that are used in it's preparation.
        Dough dough;
        Sauce sauce;
        Veggies veggies[];
        Cheese cheese;
        Pepperoni pepperoni;
        Clams clam;

        // The prepare method is now abstract.
        // This is where the ingredients needed for the pizza will be collected.
        // The ingredients will come from the ingredient factory.
        abstract void prepare();

        void bake() {
                System.out.println("Bake for 25 minutes at 350");
        }

        void cut() {
                System.out.println("Cutting the pizza into diagonal slices");
        }

        void box() {
                System.out.println("Place pizza in official PizzaStore box");
        }

        void setName(String name) {
                this.name = name;
        }

        String getName() {
                return name;
        }

        public String toString() {
                StringBuffer result = new StringBuffer();
                result.append("---- " + name + " ----\n");
                if (dough != null) {
                        result.append(dough);
                        result.append("\n");
                }
                if (sauce != null) {
                        result.append(sauce);
                        result.append("\n");
                }
                if (cheese != null) {
                        result.append(cheese);
                        result.append("\n");
                }
                if (veggies != null) {
                        for (int i = 0; i < veggies.length; i++) {
                                result.append(veggies[i]);
                                if (i < veggies.length-1) {
                                        result.append(", ");
                                }
                        }
                        result.append("\n");
                }
                if (clam != null) {
                        result.append(clam);
                        result.append("\n");
                }
                if (pepperoni != null) {
                        result.append(pepperoni);
                        result.append("\n");
                }
                return result.toString();
        }
}

In the previous implementation (factory method), we had NYStyleCheesePizza and ChicagoStyleCheesePizza. In the two classes, the only thing that differs is the use of local ingredients. The other parts (dough, sause, cheese) are the same. The same goes for NYStyleVeggiePizza and ChicagoStyleVeggiePizza, and other combination of pizzas. They follow the same preparation steps. They just have different ingredients.

So we don’t really need two classes for each pizza: we are going to use the ingredient factory to handle the regional differences.

CheesePizza:

public class CheesePizza extends Pizza {
        PizzaIngredientFactory ingredientFactory;

        public CheesePizza(PizzaIngredientFactory ingredientFactory) {
                this.ingredientFactory = ingredientFactory;
        }

        void prepare() {
                System.out.println("Preparing " + name);
                dough = ingredientFactory.createDough();
                sauce = ingredientFactory.createSauce();
                cheese = ingredientFactory.createCheese();
        }
}

ClamPizza:

public class ClamPizza extends Pizza {
        PizzaIngredientFactory ingredientFactory;

        public ClamPizza(PizzaIngredientFactory ingredientFactory) {
                this.ingredientFactory = ingredientFactory;
        }

        void prepare() {
                System.out.println("Preparing " + name);
                dough = ingredientFactory.createDough();
                sauce = ingredientFactory.createSauce();
                cheese = ingredientFactory.createCheese();
                clam = ingredientFactory.createClam();
        }
}

Revisting the NYPizzaStore:

public class NYPizzaStore extends PizzaStore {

        protected Pizza createPizza(String item) {
                Pizza pizza = null;
                PizzaIngredientFactory ingredientFactory =
                        new NYPizzaIngredientFactory();

                if (item.equals("cheese")) {

                        pizza = new CheesePizza(ingredientFactory);
                        pizza.setName("New York Style Cheese Pizza");

                } else if (item.equals("veggie")) {

                        pizza = new VeggiePizza(ingredientFactory);
                        pizza.setName("New York Style Veggie Pizza");

                } else if (item.equals("clam")) {

                        pizza = new ClamPizza(ingredientFactory);
                        pizza.setName("New York Style Clam Pizza");

                } else if (item.equals("pepperoni")) {

                        pizza = new PepperoniPizza(ingredientFactory);
                        pizza.setName("New York Style Pepperoni Pizza");

                }
                return pizza;
        }
}

Overview of what we have done:

  1. We provided a means of creating a family of ingredients for pizzas by introducing a new type of factory called an Abstract Factory.
  2. The Abstract Factory gives an interface for creating a family of products. By writing code that uses this interface, we decouple our code from the actual factory that creates the products. That allows us to implement a variety of factories that produce products meant for different contexts - like different regions, different operating systems, or different look and feels.
  3. Because our code is decoupled from the actual products, we can substitute different factories to get different behaviors.

Class diagram

                  ┌───────────────────┐                          ┌─────────────┐
                  │   <<interface>>   │                          │             │
                  │                   │◄─────────────────────────┤   Client    │
                  │ AbstractFactory   │                          ├─────────────┤
                  ├───────────────────┤                          │             ├──────────────┐
                  │                   │                          │             │              │
                  │  CreateProductA() │                          └────────┬────┘              │
           ┌──────┤                   ├───┐                               │                   │
           │      │  CreateProductB() │   │                               │                   │
           │      └───────────────────┘   │                               │                   │
           │                              │                               │                   │
           │                              │                               │                   │
           ▼                              ▼                               │                   │
    ┌──────────────────┐        ┌──────────────────┐                      │                   │
    │ ConcreteFactory1 │        │ ConcreteFactory2 │                      │                   │
    ├──────────────────┤        ├──────────────────┤                      │                   │
    │                  │        │                  │                      │                   │
    │ CreateProductA() │        │ CreateProductA() │                      │                   │
    │                  │        │                  │                      │                   │
    │                  │        │                  │                      │                   │
┌───┤ CreateProductB() │        │ CreateProductB() ├────┐                 │                   │
│   └───────┬──────────┘        └───────┬──────────┘    │                 │                   │
│           │                           │               │                 │                   │
│           │                 ┌─────────┼───────────────┼────┐            │                   │
│           │                 │         │               │    │            │                   │
│           ▼                 │         ▼               │    │            ▼                   │
│     ┌───────────┐           │   ┌───────────┐         │    │    ┌──────────────────┐        │
│     │ ProductA1 ├───────────┘   │ ProductA2 ├─────────┼────┼───►│ <<interface>>    │        │
│     ├───────────┤               ├───────────┤         │    │    │                  │        │
│     │           │               │           │         │    └───►│ AbstractProductA │        │
│     └───────────┘               └───────────┘         │         ├──────────────────┤        │
│                                                       │         │                  │        │
│                                                       │         └──────────────────┘        │
│     ┌───────────┐               ┌───────────┐         │                                     │
│     │ProductB1  ├──────────┐    │ProductB2  ├─────────┼─────┐                               │
├────►├───────────┤          │    ├───────────┤ ◄───────┘     │   ┌──────────────────┐        │
      │           │          │    │           │               └──►│ <<interface>>    │        │
      └───────────┘          │    └───────────┘                   │                  │        │
                             │                                ┌──►│ AbstractProductB │◄───────┘
                             └────────────────────────────────┘   ├──────────────────┤
                                                                  │                  │
                                                                  └──────────────────┘

Factory Methods lurking inside the Abstract Factory

Often, the methods of an Abstract Factory are implemented as factory methods. The job of an Abstract Method is to define an interface for creating a set of products. Each method in that interface is responsible for creating a concrete product, and we implement a subclass of the Abstract Factory to supply those implementations. So, factory methods are a natural way to implement your product methods in your abstract factories.

Abstract Factories’ concrete factories often implement a factory method to create their products. In Abstract Factory’s case, they are used purely to create products. In Factory Pattern’s case, it usually implements code in the abstract creator that makes use of the concrete types the subclasses create.


Links to this note