2

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");
    }
}
nirmalgyanwali
  • 624
  • 1
  • 6
  • 14
  • You have the book in your hands. How come it doesn't answer your question? It's supposed to **teach** you the O/C principle, not make you wonder about it. – Marko Topolnik Oct 14 '12 at 13:00
  • @Marko I completed the chapter Factory Pattern, though I couldn't solve it. :) – nirmalgyanwali Oct 14 '12 at 13:14
  • It is a question at the end of the chapter, then? – Marko Topolnik Oct 14 '12 at 13:15
  • I am new to this OO Design pattern. So, not getting idea. – nirmalgyanwali Oct 14 '12 at 13:23
  • For the record, I don't have much regard for that book. Its teaching style is quite confusing and the examples overly complex. Truth be told, it really is hard to teach stuff that only starts making sense in quite complex programs. This stuff is best learnt from experience and having the basic ideas floating around your head helps, but you won't get to the bottom of them by solving book examples. – Marko Topolnik Oct 14 '12 at 13:28
  • @Marko Topolnik I have read OP's book and agree with you that it is not particularly good. With regards to OPs question about how adding further types of Pizza to the createPizza method violates the Open/Closed principle, the book acknowledges that this is a problem and even rhetorically asks "By moving this elsewhere aren't we just re-homing the problem?" To this the book simply answers "yes" and does not offer a solution to this, instead the book moves on to talk about Abstract Factories. I anticipate this is why OP raised this question. – 8bitjunkie Dec 18 '14 at 14:10

6 Answers6

9

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.

8bitjunkie
  • 12,793
  • 9
  • 57
  • 70
  • 1
    And if you want to remove an old pizza? – 4imble Jan 12 '15 at 14:59
  • 1
    @4imble When existing products change or are removed from a catalogue, I think that is where the factory pattern breaks down from an OCP viewpoint, unless the definition and resolution of products is made dynamic. On that point, I've updated the reflection example to comment on how a Pizza could be removed from the menu using this approach. – 8bitjunkie Jan 12 '15 at 16:14
0

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...

HankTheTank
  • 537
  • 5
  • 15
0

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

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.

jags
  • 2,022
  • 26
  • 34
  • 6
    "You should only need to modify Concrete Factory class" - OPs question is to find a way to expand the capability of ChicagoPizzaStore without making any coding changes to it (which would violate OCP). I don't believe you have answered OP's question. – 8bitjunkie Dec 18 '14 at 14:31
-1

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.

James Matt
  • 39
  • 1
  • 2
    Two things to mention. First thing: The performance of reflection is irrelevant in the context of OP's question - it is a valid solution to the problem as described. Second thing: You have provided a link detailing "other simple techniques" but the solution to the SimpleFactory problem that your link uses is actually reflection. – 8bitjunkie Dec 18 '14 at 16:38
-2

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