The Factory Pattern

Duck duck = new MallardDuck();

We want to use interfaces to keep code flexible.

But we have to create an instance of a concrete class!

Duck duck;

if (picnic) {
    duck = new MallardDuck();
} else if(hunting) {
    duck = new DecoyDuck();
} else if(inBathTub) {
    duck = new RubberDuck();
}

We have a bunch of different duck classes, and we don't know until runtime which one we need to instantiate.

Cutting-edge pizza store

Identify the aspect that vary and separate them from what stays the same.

Remember that designs should be "open for extension but closed for modification."

Problems?

Pizza orderPizza() {
    Pizza pizza = new Pizza();

    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();

    return pizza;
}

But for flexibility, we really want Pizza to be an abstract class or interface.

Pizza orderPizza(String type) {
    Pizza pizza;

    if (type.equals("cheese")) {
        pizza = new CheesePizza();
    } else if (type.equals("greek")) {
        pizza = new GreekPizza();
    } else if (type.equals("pepperoni")) {
        pizza = new PepperoniPizza();
    }

    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();

    return pizza;
}

Based on the type of pizza, we instantiate the correct concrete class and assign it to the pizza instance variable. Note each pizza here has to implement the Pizza interface.

But the pressure is on to add more pizza types...

Pizza orderPizza(String type) {
    Pizza pizza;

/*********This is what varys************/
    if (type.equals("cheese")) {
        pizza = new CheesePizza();
    //you haven't been selling many  
    //GreekPizza lately, so you take it off.
    //} else if (type.equals("greek")) {
    //    pizza = new GreekPizza();
    } else if (type.equals("pepperoni")) {
        pizza = new PepperoniPizza();
    } else if (type.equals("clam")) {
        pizza = new ClamPizza();
    } else if (type.equals("veggie")) {
        pizza = new VeggiePizza();
    }
/*********This is what varys************/

/***This is what we expect to stay the same.***/
    Pizza.prepare();
    Pizza.bake();
    Pizza.cut();
    Pizza.box();

    return pizza;
/***This is what we expect to stay the same.***/
}

This code is Not closed for modification. If the Pizza Shop changes its pizza offerings, we have to get into this code and modify it.

We know what is varying and what isn't, it's probably time to encapsulate it.

We've got the name for this new object: we call it Factory.

public class SimplePizzaFactory {
    public Pizza createPizza(String type) {
        Pizza pizza;

        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        } else if (type.equals("clam")) {
            pizza = new ClamPizza();
        } else if (type.equals("veggie")) {
            pizza = new VeggiePizza();
        }  

        return pizza;
    }
}
public class PizzaStore {
    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory) {
        this.factory = factory;
    }

    public Pizza orderPizza(String type) {
        Pizza pizza;

        pizza = factory.createPizza(type);
    
        Pizza.prepare();
        Pizza.bake();
        Pizza.cut();
        Pizza.box();

        return pizza;
    }
}

Now it's time to fix up our client code.

Notice that we've replaced the new operator with a create method on the factory object. No more concrete instantiation here!

This isn't a REAL pattern, but it's commonly used.

Franchising the pizza store

NYPizzaFactory nyFactory = new NYPizzaFactory();
Pizza nyStore = new PizzaStore(nyFactory);
nyStore.order("Veggie");

ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
chicagoStore.order("Veggie");

Our main function should look like this

public abstract class PizzaStore {
    
    public Pizza orderPizza(String type) {
        Pizza pizza;

        pizza = createPizza(type);
    
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    //Now we've moved our factory object to this method.
    abstract createPizza(String type);
}

Now createPizza is back to being a call to a method in the PizzaStore rather than on a factory object.

A framework for the pizza store

Allowing the subclasses to decide

Pizza is abstract, orderPizza() has no idea what real concrete classes are involved. In other word, it's decoupled!!!

Which kind of pizza will be made? That's decided by the choice of pizza store you order from.

public class NYPizzaStore extends PizzaStore {
    public Pizza createPizza(String item) {
        Pizza pizza;

        if (item.equals("cheese")) {
            return new NYStyleCheesePizza();
        } else if (item.equals("veggie")) {
            return new NYStyleVeggiePizza();
        } else if (item.equals("clam")) {
            return new NYStyleClamPizza();
        } else if (item.equals("pepperoni")) {
            return new NYStylePepperoniPizza();
        } 
    }
}

Let's make pizza store

abstract Product factoryMethod(String type)

A factory method is abstract so the subclasses are counted on to handle object creation.

A factory method returns a Product that is typically used within methods defined in the superclass.

A factory method isolates the client (the code in the superclass, like orderPizza()) from knowing what kind of concrete Product is actually created.

A factory method may be parameterized (or not) to select among several variations of a product.

We're just missing one thing: PIZZA !!!

public abstract class Pizza {
    String name;
    String dough;
    String sauce;
    ArrayList toppings = new ArrayList();

    void prepare() {
        System.out.println("Preparing " + name);
        System.out.println("Tossing dough...");
        System.out.println("Adding sauce...");
        System.out.println("Adding toppings: ");
        for (int i = 0; i < toppings.size(); i++) {
            System.out.println("    " + toppings.get(i));
        }
    }

    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");
    }

    public String getName() {
        return name;
    }
}
public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
        name = "NY Style Sauce and Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";

        toppings.add("Grated Reggiano Cheese");
    }
}
public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza() {
        name = "Chicago Style Deep Dish Cheese Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";

        toppings.add("Shredded Mozzarella Cheese");
    }

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

It's finally time to meet the Factory Method Pattern

The Creator classes

The Product classes

Another Perspective: parallel class hierarchies

Factory Method Pattern Defined

The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Mothod lets a class defer instantiation to subclasses.

A very dependent PizzaStore

public class DependentPizzaStore {
    public Pizza createPizza(String style, String type) {
        Pizza pizza;

        if(style.equals("NY")) {
            if (type.equals("cheese")) {
                return new NYStyleCheesePizza();
            } else if (type.equals("veggie")) {
                return new NYStyleVeggiePizza();
            } else if (type.equals("clam")) {
                return new NYStyleClamPizza();
            } else if (type.equals("pepperoni")) {
                return new NYStylePepperoniPizza();
            } 
        }
        else if(style.equals("Chicago")) {
            if (type.equals("cheese")) {
                return new ChicagoStyleCheesePizza();
            } else if (type.equals("veggie")) {
                return new ChicagoStyleVeggiePizza();
            } else if (type.equals("clam")) {
                return new ChicagoStyleClamPizza();
            } else if (type.equals("pepperoni")) {
                return new ChicagoStylePepperoniPizza();
            } 
        }
        else {
            System.out.println("Error: invalid type of pizza");
        }
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

Looking at object dependencies

The Dependency Inversion Principle

Design Principle

Depend upon abstractions. Do not depend upon concrete classes.

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Applying the Principle

Why is it called dependency inversion?

Both high-level and low-level modules now depending on the abstraction.

Because it inverts the way you typically might think about your OO design — high-level should depend upon low-level modules.

A few guildlines to help you follow the Principle...

1. No variable should hold a reference to a concrete class.

2. No class should derive from a concrete class.

3. No method should override an implemented method of any of its base classes.

Remember: This is a guideline you should strive for, rather than a rule you should follow all the time.

A few franchises have been subsituting inferior ingredients in their pies to lower cost and increase their margins.

Building the ingredient factories

public interface PizzaIngredientFactory {
    Function createDough();
    Function createSauce();
    Function createCheese();
    Function createVeggies();
    Function createPepperoni();
    Function createClam();
}

Here's what we're going to do:

1. Build a factory for each region. To do this, you'll create a subclass of PizzaIngredientFactory that implements each methods.

2. Implement a set of ingredient classes to be used with the factory, like ReggianoCheese, RedPeppers, and ThickCrustDough. These classes can be shared among regions where appropriate.

3. Then we still need to hook all this up by working our new ingredient factories into our old PizzaStore code.

Building the New York ingredient factory

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 createClams() {
        return new FreshClams();
    }
}
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 Spinach(), new BlackOlives(), new Eggplant() };
        return veggies;
    }


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

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

Building the Chicago ingredient factory

public abstract class Pizza {
    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clam;

    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");
    }

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

    public String getName() {
        return name;
    }
}

Reworking the pizzas...

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();
    }
}

Reworking the pizzas, continued...

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();
        //If it's a New York factory, the clams 
        //will be fresh; if it's Chicago, they'll be frozon.
        clam= ingredientFactory.createClam();
    }
}

Reworking the pizzas, continued...

public class NYPizzaStore extends PizzaStore {
    protected Pizza createPizza(String item) {
        Pizza pizza;

        PizzaIngredientFactory ingredientFactory = new NYIngredientFactory();

        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;
    }
}

Revisting our pizza stores

What have we done?

Abstract Factory Pattern defined

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

Factory Method and Abstract Factory compared

Abstract Factory

Provide an interface for creating families of related or dependent objects without specifying their concrete class.

Factory Method

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to the subclasses.

Through object composition

Through inheritance

The Factory Pattern

By TingSheng Lee

The Factory Pattern

  • 455