Design pattern notes factory method pattern and abstract factory pattern

Design pattern

Factory method pattern and abstract factory pattern

Scenario assumption

There is a pizza shop to sell pizza, which requires a process. Because pizza has several types, you can implement the following code:
public class PizzaStore {
    public void orderPizza(String type) {
        Pizza pizza;
        if ("a".equals(type)) {
            pizza = new APizza();
        } else if ("b".equals(type)) {
            pizza = new BPizza();
        } else {
            pizza = null;
        }

        if (pizza != null) {
            pizza.cut();
            pizza.box();
            pizza.finish();
        }

    }
}

The above procedure seems to be OK. However, the first thing to consider after the program is written is whether this part of the requirements will change!

Hypothesis: the store wants to add 2 new flavors of pizza and delete 1 kind of pizza that is not sold well.

At this point, you need to modify the above code. However, this violates design principles - open to extensions, closed to changes. You know, requirements usually change, and changing code over and over often makes it hard to maintain!

Consider one of the design principles of the strategic design pattern - encapsulating the parts that need to change. Create a new "factory class" to handle the requirements of creating objects, as follows:

public class PizzaFactory {
    /*
    Not a static method
    */
    public Pizza createPizza(String type) {
        Pizza pizza;
        if ("a".equals(type)) {
            pizza = new APizza();
        } else if ("b".equals(type)) {
            pizza = new BPizza();
        } else {
            pizza = null;
        }
        return pizza;
    }
}

Accordingly, modify the code of PizzaStore.java as follows:

public class PizzaStore {
    private PizzaFactory factory;

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

    public void orderPizza(String type) {
        //Using pizza factory to create objects
        Pizza pizza = factory.createPizza(type);
        if (pizza != null) {
            pizza.cut();
            pizza.box();
            pizza.finish();
        }

    }
}

The above code encapsulates the changed part, which is conducive to code reuse. Now the demand has changed again, as follows:

Hypothesis: Pizza shops are on fire, many regions in China want to join in, but different provinces need to make different flavors of pizza to meet the needs of local people.

At this point, you can have each franchise store corresponding to a specific factory to create pizza. As follows:

public abstract class PizzaFactory {
    public abstract Pizza createPizza(String type);
}

public class APizzaFactory extends PizzaFactory{
    @Override
    public Pizza createPizza(String type) {
        Pizza pizza;
        if ("c".equals(type)) {
            pizza = new APizza();
        } else if ("d".equals(type)) {
            pizza = new BPizza();
        } else {
            pizza = null;
        }
        return pizza;
    }
}

In the above process, businesses in area a can directly use their APizzaFactory to obtain a pizza object, and then businesses can decide whether to box or cut, that is, they can not need the PizzaStore. So we can't control the whole pizza making process. In order to strengthen quality control, we can abstract the pizza store into an abstract class, and then add the factory method to make pizza, so that businesses can implement this class.

public abstract class PizzaStore {

    public void orderPizza(String type) {
        //decoupling
        Pizza pizza = createPizza(type);
        if (pizza != null) {
            pizza.cut();
            pizza.box();
            pizza.finish();
        }

    }
    //Factory method
    protected abstract Pizza createPizza(String type);

}

public class APizzaStore extends PizzaStore {
    @Override
    public Pizza createPizza(String type) {
        Pizza pizza;
        if ("c".equals(type)) {
            pizza = new APizza();
        } else if ("d".equals(type)) {
            pizza = new BPizza();
        } else {
            pizza = null;
        }
        return pizza;
    }
}

As you can see, factory methods let subclasses determine the specific objects to be created, thus encapsulating the object creation process.

Factory method mode

Define an interface to create an object in the class, and let the subclass decide which specific object to create. Thus, the instantiation of the object is pushed to the subclass.

Dependency inversion

Rely on abstraction, not concrete classes.

That is, don't let high-level components rely on low-level components, and both components should rely on abstraction.
At the beginning of the code, the Pizza store relies on all the specific Pizza. The dependency of subsequent code is reduced, because we make the high-level component Pizza store depend on the abstraction of the low-level component specific Pizza. This is the effect of the factory method pattern.

Inverted thinking mode

The thinking from the top to the bottom is: need a pizza shop class, this class can make all kinds of pizza, cut, pack
Inversion: you need to make all kinds of pizzas. They should have a common interface Pizza. Pizzas are made by relying on the abstract interface, and specific Pizza classes also depend on the interface. The interface to create a specific Pizza is defined as returning the type.

Guideline

  • Variables do not hold references to specific classes
    We need to program for interfaces, that is, relying on abstraction.
  • Don't let classes inherit concrete classes
    If we inherit concrete, we must depend on concrete classes.
  • Do not override methods already implemented in the base class
    If the subclass covers the methods in the base class, the base class is not suitable for inheritance

hypothesis

We don't want each franchise store to decide on its own, but we want the designated raw material factory to provide it.

Add a new method prepare to prepare raw materials.

public abstract class Pizza {
    //Some raw material
    private Sault sault;

    public void finish() {

    }
    public void cut() {

    }
    public void box() {

    }

    public abstract void prepare();
}

public interface Sault {
}

Raw materials of different factories are used in different regions, so a raw material factory interface can be set:

public interface IngredientFactory {
    Sault createSault();
}

public class AIngredientFactory implements IngredientFactory {
    @Override
    public Sault createSault() {
        return new Sault(){
            @Override
            public String toString() {
                return "a sault";
            }
        };
    }
}

Specific pizza making:

public class APizza extends Pizza {
    private Sault sault;
    private IngredientFactory ingredientFactory = new AIngredientFactory();
    @Override
    public void prepare() {
        sault = ingredientFactory.createSault();
    }
}

Abstract factory pattern

Provides an interface to create a family of related or dependent objects without specifying the specific class.

For example, the above-mentioned ingredientfactory. Createdefault() does not specify which specific Sault to create. Our client code, APizza, is also decoupled from the concrete Sault.

Published 9 original articles, praised 0, visited 604
Private letter follow

Tags: Java

Posted on Sun, 08 Mar 2020 07:05:00 -0400 by SpectralDesign.Net