Question

I am trying to learn Object Oriented Design pattern using Head First Design Pattern. Here is one example of factory pattern from the book, where i want to add new pizza item without violating the open closed principle. In the given example code from the book, if i add new pizza item class, I need to modify the PizzaStore and PizzaOrder class. But I just want to add new Pizza Item without modifying other classes.

public class ChicagoPizzaStore extends PizzaStore {

Pizza createPizza(String item) {
        if (item.equals("cheese")) {
                return new ChicagoStyleCheesePizza();
        } else if (item.equals("veggie")) {
                return new ChicagoStyleVeggiePizza();
        } else if (item.equals("clam")) {
                return new ChicagoStyleClamPizza();
        } 
            else return null;
}

}

This pizzaStore class is to create and order the pizza.

public abstract class PizzaStore {

    abstract Pizza createPizza(String item);

public Pizza orderPizza(String type) {
    Pizza pizza = createPizza(type);
    System.out.println("--- Making a " + pizza.getName() + " ---");
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

}

This is the abstract Pizza class:

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

This class is used to take the order from the customer.

 public class PizzaTestDrive {

        public static void main(String[] args) {
            PizzaStore nyStore = new NYPizzaStore();
            PizzaStore chicagoStore = new ChicagoPizzaStore();

            Pizza pizza = nyStore.orderPizza("cheese");
            System.out.println("Ethan ordered a " + pizza.getName() + "\n");
    }
}

This is my new pizza item class. I want to order this pizza item without modifying chicagoPizzaStore and testDrive class:

public class ChicagoStyleClamPizza extends Pizza {
    public ChicagoStyleClamPizza() {
        name = "Chicago Style Clam Pizza";
        dough = "Extra Thick Crust Dough";
        sauce = "Plum Tomato Sauce";
        toppings.add("Shredded Mozzarella Cheese");
        toppings.add("Frozen Clams from Chesapeake Bay");
    }

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

Solution

The point of Factory Method design pattern is to make client and concrete product loosely coupled. Client interacts with only interface or base class. Thus in future if you need to add new concrete product class (in your case Pizza), the new class should not result in changes in the client code (in your case PizzaTestDrive). For adding a new product (Pizza), you should only need to modify Concrete Factory class (in your case ChicagoPizzaStore).

I think your implementation of Factory Method design pattern is correct. For adding a new Pizza, the client code is not going to change, only Concrete Factory class is changing.

OTHER TIPS

As it stands, each time ChicagoPizzaStore come out with a new type of pizza (new subclass of Pizza), you will need to add more capability to the concrete creator method createPizza(String item) to enable the Pizza store to be able to create those types of pizza.

As you have identified, this violates the OCP.

Below follows two solutions to this problem.

1. Use reflection within createPizza(String item) to dynamically create pizzas

This solution will require you to violate the principles of OCP one final time, however using reflection to dynamically create Pizza instances means that ChicagoPizzaStore will, beyond this change, no longer need to be modified to support future flavours of Pizza.

The names of the new types of Pizza class must match the name of the key (item argument) provided to the create pizza method. The solution works like so:

public class ChicagoPizzaStore extends PizzaStore {

Pizza createPizza(String item) {
        try {
            //some assumptions about the classpath locations made here
            return Class.forName(item).newInstance();
        } catch(Exception e) {
            return null;
        }
}

When new types of Pizza are created, these can simply be passed in as a key to the createPizza(item) method and they will be created.

Similarly, if a type of Pizza is taken off the menu, removing the class definition of the such a Pizza from the classpath will result in createPizza(item) returning null for the discountinued flavour.

The use of reflection has its critics for a variety of reasons, but critique of reflection is outside of the scope of this question and it is nether-the-less an entirely valid solution to the problem of implementing factories which adhere to the Open/Closed principle.

2. Subclass ChicagoPizzaStore

As the O in SOLID states, classes are "open for extension and closed for modification". Thus, a solution to your problem is simply to extend ChicagoPizzaStore:

public class ExtendedChicagoPizzaStore extends ChicagoPizzaStore {

    Pizza createPizza(String item) {
            if (item.equals("spicy")) {
                    return new RidiculouslySpicyPizza();
            } else {
                    return super.createPizza(item);
           }
    }

This solution has the advantage that OCP is not violated in order to apply it.

Having a switch statement kind of breaks OC. To fix that maybe polymorphism is the way to go. Maybe an abstract factory?

Or maybe a factory is wrong in general and you want to use a builder pattern. After all a pizza is a pizza is a pizza. So you just have to build them differently. Like with a StringBuilder...

Here is a running example

class FactoryClosedForModification {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
            ShapeFactory sf = new ShapeFactory();
            Shape shape = (Shape) sf.getShape(Triangle.class.getName());
            shape.draw();
        }
    }


class ShapeFactory {

    public Object getShape(String shapeName)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        return Class.forName(shapeName).newInstance();
    }
}

class Shape {
    public void draw() {
        System.out.println("Drawing a shape.");
    }
}

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Triangle.");
    }
}

class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Drawing Circle.");
    }
}

If you use reflection to satisfy open-closed principle, you are compromising performance. Rather you can use other simple techniques to make your factory according to open-closed principle. Factory Design Patterns and Open-Closed Principle (OCP), the ‘O’ in SOLID gives a more proper explanation for this. The article also tells how to tweak simple factory to obey open closed principle.

Using reflection in your case is not really good. It's better to use something like properties file for ChicagoPizzaStore to map item on class... for example:

cheese=ChicagoStyleCheesePizza
veggie=ChicagoStyleVeggiePizza
clam=ChicagoStyleClamPizza
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top