Factory Design Pattern – Head First Approach

Factory Design Pattern – Head First Approach

Creating Objects with Ease Using the Factory Pattern

The Factory Pattern is another fundamental design pattern that is crucial for creating objects in a flexible and decoupled manner. In the Head First Design Patterns book, the Factory Pattern is explained through the analogy of a pizza store, where you can "order" different types of pizzas without worrying about the specific details of how each one is made. This approach encapsulates the object creation logic, promoting cleaner, more maintainable code.

Let’s dive into the Factory Pattern with examples and concepts inspired by the Head First book.


What is the Factory Pattern?

Definition: The Factory Pattern defines an interface for creating objects, but allows subclasses to alter the type of objects that will be created. It helps in decoupling the client from the concrete classes used to instantiate objects.

The idea is to centralize the creation of objects, allowing the client code to request objects through a generic interface, without being tightly coupled to specific classes. This pattern is particularly useful when your system needs to handle multiple types of related objects.


Problem Scenario

Imagine you own a pizza store that serves different styles of pizza such as New York Style, Chicago Style, and California Style. You don’t want to tie your store to any specific pizza style implementation. With the Factory Pattern, you can create a "Pizza Store" that can make pizzas without needing to know the exact details of each type of pizza. Instead, the store delegates the responsibility of pizza creation to a factory, which knows how to create each specific pizza.


Using the Factory Pattern

The Factory Pattern can be implemented in various forms, such as the Simple Factory, Factory Method, or Abstract Factory. We'll explore the Factory Method pattern here, which is more flexible and allows subclasses to determine the specific object to be created.

Step 1: Create the Product (Pizza)

First, create a Pizza class, which will be the "product" that the factory will produce.

public abstract class Pizza {
    String name;
    String dough;
    String sauce;

    void prepare() {
        System.out.println("Preparing " + name);
    }

    void bake() {
        System.out.println("Baking " + name);
    }

    void cut() {
        System.out.println("Cutting " + name);
    }

    void box() {
        System.out.println("Boxing " + name);
    }

    public String getName() {
        return name;
    }
}

Step 2: Create Concrete Products

Next, create concrete classes for specific types of pizzas, such as NewYorkStyleCheesePizza and ChicagoStyleCheesePizza.

public class NewYorkStyleCheesePizza extends Pizza {
    public NewYorkStyleCheesePizza() {
        name = "New York Style Sauce and Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";
    }
}

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

Step 3: Create the Creator (PizzaStore)

Now, create the PizzaStore class that defines the orderPizza method. The actual creation of the pizza is delegated to a factory method, createPizza(), which will be implemented by subclasses.

public abstract class PizzaStore {

    public Pizza orderPizza(String type) {
        Pizza pizza;

        pizza = createPizza(type);

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

        return pizza;
    }

    protected abstract Pizza createPizza(String type);
}

Step 4: Implement Concrete Creators

Create subclasses of PizzaStore that implement the createPizza() method to instantiate specific types of pizzas.

public class NewYorkPizzaStore extends PizzaStore {
    protected Pizza createPizza(String item) {
        if (item.equals("cheese")) {
            return new NewYorkStyleCheesePizza();
        } else return null;
    }
}

public class ChicagoPizzaStore extends PizzaStore {
    protected Pizza createPizza(String item) {
        if (item.equals("cheese")) {
            return new ChicagoStyleCheesePizza();
        } else return null;
    }
}

Step 5: Simulate Ordering Pizzas

Finally, simulate a pizza order where different styles of pizzas are created using the Factory Method pattern.

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

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

        pizza = chicagoStore.orderPizza("cheese");
        System.out.println("Joel ordered a " + pizza.getName() + "\n");
    }
}

Output:

Preparing New York Style Sauce and Cheese Pizza
Baking New York Style Sauce and Cheese Pizza
Cutting New York Style Sauce and Cheese Pizza
Boxing New York Style Sauce and Cheese Pizza
Ethan ordered a New York Style Sauce and Cheese Pizza

Preparing Chicago Style Deep Dish Cheese Pizza
Baking Chicago Style Deep Dish Cheese Pizza
Cutting Chicago Style Deep Dish Cheese Pizza
Boxing Chicago Style Deep Dish Cheese Pizza
Joel ordered a Chicago Style Deep Dish Cheese Pizza

Bullet Points

  • Encapsulation of object creation: The Factory Pattern centralizes object creation in one place, making the code easier to maintain and modify.

  • Open for extension, closed for modification: New types of products can be introduced by extending the factory method without changing the core code of the application.

  • Decouples client from concrete implementations: The client (in this case, the PizzaStore) doesn’t need to know about the specific type of product being created, reducing dependency.


When to Use the Factory Pattern

  • When a class cannot anticipate the type of objects it needs to create.

  • When you want to centralize the creation logic to promote code reuse and flexibility.

  • When you want to decouple the code that creates an object from the code that uses the object.


Conclusion

The Factory Pattern is an essential tool for managing object creation in a clean and flexible way. By using this pattern, you can decouple the instantiation process from the rest of your application, making it easier to extend, maintain, and modify. With the example of a pizza store from the Head First Design Patterns book, the power of the Factory Pattern becomes clear: it simplifies complex object creation and keeps your code scalable.