Learning Notes of Head first Design Patterns - Abstract Factory Patterns

The abstract factory pattern provides an interface for creating families of related or dependent objects without specifying specific classes explicitly.

Ensure consistency of raw materials

The key to the success of pizza shops is fresh and high quality raw materials. How to ensure that each franchise stores use high quality raw materials? You plan to build a factory that generates raw materials and deliver them to franchise stores. There's still one problem with this approach: franchise stores are located in different regions, and red sauce in New York is different from that in Chicago. Franchise stores have the same family of products (Italian sausages, sauces, cheese, vegetables, etc.), but the production methods vary according to the region. So for New York and Chicago, you have two different sets of raw materials. If there will be franchise stores in California soon, will there be a need for a new set of raw materials?

Building Raw Material Factory

Now, we are going to build a factory to produce raw materials, which will be responsible for creating every raw material in the raw material family. That is to say, the factory will need to produce dough, sauce, cheese and so on.

Start by defining an interface for the factory, which is responsible for creating all the raw materials:

public interface PizzaIngredientFactory {
    // In the interface, each raw material has a corresponding method to create the raw material.
    public Dough createDough();
    public Sauce createSauce();
    public Cheese createCheese();
    public Veggies[] createVeggies();
    public Pepperoni createPepperoni();
    public Clams createClams();
}
What we need to do now is:
1. Build a factory for each area. You need to create a subclass inherited from Pizza IngredientFactory to implement each creation method.
2. Implement a set of raw material classes for factory use, such as Reggiano Cheese, Red Peppers, ThickCrust Dough. These classes can be shared among suitable regions.
3. Then you need to organize everything and integrate the new raw material factory into the old Pizza Store code.

Creating New York Raw Materials Factory

This is the realization of the New York Raw Material Factory. The factory specializes in garlic tomato sauce, Reggiano cheese and fresh clams.

public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
    // For each of the raw materials in the raw material family, we offer a New York version.
    @Override
    public Dough createDough() {
        return new ThinCrushDough();
    }
 
    @Override
    public Sauce createSauce() {
        return new MarinaraSauce();
    }
 
    @Override
    public Cheese createCheese() {
        return new ReggianoCheese();
    }
 
    @Override
    public Veggies[] createVeggies() {
        Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom(), new RedPepper()};
        return veggies;
    }
 
    @Override
    public Pepperoni createPepperoni() {
        // This is a sliced Italian sausage, which will be used in New York and Chicago.
        return new SlicedPepperoni();
    }
 
    @Override
    public Clams createClams() {
        // New York is near the sea, so there are fresh clams. Chicago must use frozen clams
        return new FreshClams();
    }
}

Remake pizza

The factory is ready to produce high quality raw materials. Now we just need to make Pizza again so that they can only use the raw materials produced by the factory. Let's start with the abstract Peizza class:

public abstract class Pizza {
    String name;
    // Each pizza holds a set of ingredients for preparation.
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clams;
 
    // Now declare the prepare() method as abstract. In this way, we need to collect the raw materials for pizza, which of course come from the raw material factory.
    abstract void prepare();
 
    // Other ways to stay still
    void bake(){
        // ...
    }
 
    void cut(){
        // ...
    }
 
    void box(){
        // ...
    }
}
Now that there is an abstract pizza, it's time to start creating New York and Chicago-style pizzas. From now on, franchise stores must obtain raw materials directly from factories.
We've written code for factory methods, including NYCheesePizza and ChiagoCheesePizza classes. Comparing the two categories, the only difference is the use of regional raw materials, as for pizza, the same approach. They all follow the same preparation steps, using only different raw materials.
So, in fact, we don't need to design two different classes to deal with different flavors of pizza, let the raw material factory deal with this regional difference. The following is Cheese Pizza:

public class CheesePizza extends Pizza {
    PizzaIngredientFactory ingredientFactory;
 
    // To make pizza, factories need to provide raw materials.
    // So each pizza class needs to get a factory from the constructor parameters and store the factory in an instance variable.
    public CheesePizza(PizzaIngredientFactory ingredientFactory){
        this.ingredientFactory = ingredientFactory;
    }
 
    @Override
    void prepare() {
        // The prepare() method creates cheese pizza step by step, with factory requests whenever raw materials are needed.
        dough = ingredientFactory.createDough();
        sauce = ingredientFactory.createSauce();
        cheese = ingredientFactory.createCheese();
    }
}

Pizza's code uses related factories to generate raw materials. The raw materials depend on the factories used. Pizza doesn't care about the raw materials at all. It only knows how to make pizza. Now, Pizza and regional raw materials are decoupled. Whether the raw material factory is in the Rocky Mountains or in the northwest coastal areas, the Pizza class can be easily reused, no problem at all.

Back to the pizza shop

We're almost finished. We just need to make a brief tour of the franchise to make sure they're using the right pizza. They also need to be able to connect with local raw material factories:

public class NYPizzaStore extends PizzaStore {
 
    protected Pizza createPizza(String item) {
        Pizza pizza = null;
        // The New York store will use the New York Pizza Raw Material Factory, which is responsible for the production of raw materials for the use of New York-style pizza.
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
 
        if (item.equals("cheese")) {
            // Pass the factory to each pizza so that the pizza can get the raw material from the factory.
            pizza = new CheesePizza(ingredientFactory);
        } else if (item.equals("veggie")) {
            pizza = new VeggiePizza(ingredientFactory);
        } else if (item.equals("clam")) {
            pizza = new ClamPizza(ingredientFactory);
        } else if (item.equals("pepperoni")) {
            pizza = new PepperoniPizza(ingredientFactory);
        }
        return pizza;
    }
}

What have we done?

What have we done with a series of code changes?
We introduced a new type of factory, the so-called Abstract factory, to create a family of pizza ingredients.
Through the interface provided by abstract factories, we can create a family of products. Using this interface to write code, our code will be decoupled from the actual factories in order to realize various factories in different environments and produce different products. For example: different regions, different operating systems, different appearance and operation.
Because the code is decoupled from the actual product, we can replace different factories to achieve different behaviors, such as garlic tomato sauce, rather than tomato sauce.

Comparing factory methods with abstract factories

PizzaStore is implemented as a factory method because we need to create products based on regional changes. Through the factory method, each region has its own specific factory, they know how to make pizza suitable for the region.

Because we need to create a product family (that is, raw materials), we implement Pizza Ingredient Factory as an abstract factory. Each subcategory uses suppliers in its region to implement these raw materials.

The factory method uses inheritance to delegate the creation of objects to subclasses, which implement factory methods to create objects. Factory methods allow classes to defer instantiation to subclasses.

Abstract factories use object combinations, and object creation is implemented in methods exposed by factory interfaces. Abstract factories create related object families without relying on their specific classes.

All factories are used to encapsulate the creation of objects. All factory patterns promote loose coupling by reducing dependencies between applications and specific classes.

Origin: cashow

Posted by penguinmasta on Thu, 11 Apr 2019 21:48:33 -0700