5, Factory mode

Take the pizza shop as an example:

  1. You can order pizza in the pizza store;
  2. According to the requirements of customers, pizza produces different flavors.

Simple factory and static factory

Simple factory is not a design pattern, but a programming pattern; but it is often used.

Simple factory:

  • PizzaStore
/**
 * @author user
 * @date 2020/6/15 11:55
 */
public class PizzaStore {

    SimplePizzaFactory simplePizzaFactory ;

    public PizzaStore(SimplePizzaFactory simplePizzaFactory){
        this.simplePizzaFactory = simplePizzaFactory;
    }

    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = simplePizzaFactory.creatPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}

  • Simplepizzaffactory produces pizza according to different pizza categories
/**
 * @author user
 * @date 2020/6/15 12:07
 */
public class SimplePizzaFactory {

    Pizza pizza = null;

    public Pizza creatPizza(String type) {
        switch(type){
            case "cheese":
                pizza = new CheesePizza();
                break;
            case "greek":
                pizza = new GreekPizza();
                break;
            case "pepperoni":
                pizza = new PepperoniPizza();
                break;
            default:break;

        }
        return pizza;
    }
}
  • Test class
/**
 * @author user
 * @date 2020/6/15 15:24
 */
public class Test {

    public static void main(String[] args) {
        PizzaStore pizzaStore = new PizzaStore(new SimplePizzaFactory());
        pizzaStore.orderPizza("cheese");
        pizzaStore.orderPizza("greek");
        pizzaStore.orderPizza("pepperoni");
    }
}
  • Operation results

Wrap the code for creating Pizza into a class. When the implementation changes in the future, you only need to modify this class.

Static factory

The factory method is defined as a static method, so that the target object can be created without instance factory; the disadvantage is that the behavior of creating method cannot be changed by inheritance.

Factory method pattern and abstract factory pattern

Factory method mode

Factory method pattern: defines an interface for creating objects, but the subclass decides which class to instantiate. Factory methods delay class instantiation to subclasses.

Don't look down

Pizza shop franchisees, each of which can provide different flavors of pizza (such as New York, Chicago, California). If it is implemented in a simple factory way, write three different factories: NYPizzaFactory, Chicago pizzafactory, California pizzafactor. Then franchise stores all over the world have suitable factories to use. But if each franchise wants to improve its own pizza making, it can't be realized by using the factory we defined. At this time, it's good to be able to tie the franchise store and pizza creation together while maintaining a certain degree of flexibility.

Use factory method pattern: abstract the createpizza class, change the createpizza () method to abstract method and move it from factory to createpizza;

/**
 * @author user
 * @date 2020/6/15 11:55
 */
public abstract class PizzaStore {
    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = creatPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    /**
     * The abstract method of making pizza is to instantiate that kind of pizza by subclass customization
     * This method is the factory method
     * @param type
     * @return
     */
    abstract Pizza creatPizza(String type);
}

Let each franchise store inherit the PizzaStore class, realize its own createPizza, and decide the production process of pizza.

  • Production of New York franchise pizza:
/**
 * @author gu
 * @date 2020/6/18 22:41
 */
public class NYPizzaStore extends PizzaStore{

    @Override
    Pizza creatPizza(String type) {
        Pizza pizza = null;
        switch (type) {
            case "cheese":
                pizza = new NYCheesePizza();
                break;
            case "greek":
                pizza = new NYGreekPizza();
                break;
            case "pepperoni":
                pizza = new NYPepperoniPizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}
  • pizza produced by Chicago franchise store:
/**
 * @author gu
 * @date 2020/6/18 22:41
 */
public class ChicagoPizzaStore extends PizzaStore{

    @Override
    Pizza creatPizza(String type) {
        Pizza pizza = null;
        switch (type) {
            case "cheese":
                pizza = new ChicagoCheesePizza();
                break;
            case "greek":
                pizza = new ChicagoGreekPizza();
                break;
            case "pepperoni":
                pizza = new ChicagoPepperoniPizza();
                break;
            default:
                break;
        }
        return pizza;
    }
}
  • Pizza interface can be changed to abstract class to provide some basic methods for making; subclass can override some default methods of parent class to implement its own specific methods.

When there is only one concrete Creator, the factory method pattern is still useful because it helps us decouple the implementation of the product (the concrete goods created) from the use (the abstract goods). If you add products or change the implementation of products, Creator will not be affected.
Factory methods and creators are not all abstract. You can define a default factory method to produce a specific product, so that even if the creator does not have any subclasses, you can still create goods.
Factory methods often produce only one kind of object without parameterization. In the above example, different objects are generated according to type, which is called "parametric factory method". Both forms of the pattern are valid.

Dependency Inversion Principle

Let's first look at a strongly dependent pizza store:

/**
 * pizzaStore without factory mode
 *
 * @author user
 * @date 2020/6/19 11:19
 */
public class DependentPizzaStore {

    public Pizza creatPizza(String style, String type){

        Pizza pizza = null;
        if("NY".equals(style)){
            switch (type) {
                case "cheese":
                    pizza = new NYCheesePizza();
                    break;
                case "greek":
                    pizza = new NYGreekPizza();
                    break;
                case "pepperoni":
                    pizza = new NYPepperoniPizza();
                    break;
                default:
                    break;
            }
        } else if("Chicago".equals(style)){
            switch (type) {
                case "cheese":
                    pizza = new ChicagoCheesePizza();
                    break;
                case "greek":
                    pizza = new ChicagoGreekPizza();
                    break;
                case "pepperoni":
                    pizza = new ChicagoPepperoniPizza();
                    break;
                default:
                    break;
            }
        }else {
            System.out.println("Error: invalid type of pizza");
            return null;
        }
        return pizza;
    }
}


This factory creates all pizza objects and relies on all pizza concrete classes. Any changes to the pizza implementation will affect the pizza store. Each new pizza type is equivalent to one more dependency.

This leads to the dependency inversion principle: to rely on abstraction, not on concrete classes. Much like "programming for interfaces, not for implementations." It is very similar indeed. It can be said that the "dependency inversion principle" is a specific application of "programming for interfaces". But it emphasizes more on abstraction. This principle shows that high-level components cannot be dependent on low-level components. Moreover, no matter high-level components or low-level components, "both" should depend on abstraction.

A high-level component is a class whose behavior is defined by other low-level components. For example: Pizza store is a high-level component because its behavior is defined by pizza; pizza store creates all different pizza objects, and pizza implementation belongs to the underlying component.

Although a Pizza abstract class is defined, we actually create a concrete Pizza in the code, so this abstraction is useless.

The diagram after using the factory method is as follows:

It is clear that both the high-level components (pizza store) and the low-level components (various pizza) rely on the pizza abstraction. This is dependency inversion.

Rely on inversion best practices:

  1. Variables cannot hold references to specific classes;
  2. Do not let classes derive from concrete classes;
  3. Do not override implemented methods in the base class.

Abstract factory pattern

Definition: provides an interface to create a family of related or dependent objects without specifying specific classes. In this way, customers are decoupled from specific products.

Now Chicago uses one set of ingredients; New York uses another.
Each ingredient family includes a kind of dough, a kind of sauce, a kind of cheese, a kind of seafood seasoning, and some vegetables.

  • Abstract factory diagram

  • pizza uses the whole diagram of abstract factory

Implementation of pizza in abstract factory

  1. First, define an interface for the factory, which is responsible for creating all raw materials:
/**
 * Raw material factory
 * @author user
 * @date 2020/6/19 17:56
 */
public interface PizzaIngredientFactory {

    public Dough createDough();

    public Sauce createSauce();

    public Cheese createCheese();

    public Veggie[] createVeggie();

    public Pepperoni createPepperoni();

    public Clams createClams();
}

In the interface, each material has a corresponding method to create the material.

  1. Create a raw material factory for each region, implement the interface of raw material factory, and implement each creation method;

  2. Realize a set of raw materials for factory use; each raw material has interface and specific implementation



4. Integrate raw material factory into pizza store code

/**
 * @author user
 * @date 2020/6/15 11:55
 */
public abstract class PizzaStore {

    public Pizza orderPizza(String type){
        Pizza pizza = creatPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    /**
     * The abstract method of making pizza is to instantiate that kind of pizza by subclass customization
     * @param type
     * @return
     */
    abstract Pizza creatPizza(String type);
}
public class NYPizzaStore extends PizzaStore{
    @Override
    Pizza creatPizza(String type) {
        Pizza pizza = null;
        PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        switch (type) {
            case "cheese":
                pizza = new CheesePizza(ingredientFactory);
                break;
            case "veggie":
                pizza = new VeggiePizza(ingredientFactory);
                break;
            case "pepperoni":
                pizza = new PepperoniPizza(ingredientFactory);
                break;
            case "clam":
                pizza = new ClamPizza(ingredientFactory);
                break;
            default:
                break;
        }
        return pizza;
    }
}

The difference between factory method and abstract factory

First, both factory methods and abstract factories are responsible for creating objects. Decouple the customer from the actual product being used.

The factory method uses the inheritance implementation, and the subclass implements the factory method of the parent class to create the object. Subclasses are responsible for specific type creation. The code implemented in the abstract builder of factory methods usually uses the concrete types created by subclasses. The abstract factory uses an abstract type (Interface) to create a group of related objects. The disadvantage is that extending this set of related products will change all classes that implement this interface. Abstract factories use factory methods to implement specific factories.

Usage scenario:

  • Abstract factory: it can be used when you need to create a product family and the related products you want to make.
  • Factory method: when the customer code is decoupled from the concrete class to be instantiated. Or we don't know which concrete classes to instantiate yet. Just subclasses inherit and implement factory methods.

summary

  1. All factories are used to encapsulate the creation of objects.
  2. Simple factory (or static factory): although not a design pattern, it is also a simple method to decouple the client program from the concrete.
  3. Factory method: use inheritance to delegate the creation of objects to subclasses, which implement factory methods to create objects.
  4. Abstract factory: using object composition, object creation is implemented in methods exposed by factory interfaces.
  5. All factory patterns promote loose coupling by reducing dependencies between applications and concrete classes.
  6. Factory methods allow classes to defer instantiation to subclasses
  7. Abstract factories create families of related objects instead of relying on their concrete classes.
  8. The principle of Dependence Inversion guides us to avoid dependence on specific types and try to rely on abstractions.
  9. Factories help us rely on abstract programming rather than concrete class programming.

In my opinion, when programming, we should first summarize the abstractions, build the framework according to the abstract classes (high-level), and then go to some kind of programming (low-level).

Application of factory mode

There is also a lot of code in JDK that uses the factory method pattern.

iterator method in Collection

java.util.Collection The interface defines an abstract iterator() method, which is a factory method.

For the iterator() method, Collection is a root abstract factory. There are interfaces such as List as the abstract factory, and then there are specific factories such as ArrayList.

java.util.Iterator The interface is the root abstract product. There are abstract products such as ListIterator and ArrayList iterator as specific products.

Using the iterator method in different concrete factory classes can get different concrete product instances.

JDBC database development

When using JDBC for database development, if the database is changed from MySQL to Oracle or other, you only need to change the database driver name, and no other changes are needed (provided that the standard SQL statements are used). Or in the Hibernate framework, changing the database dialect is similar.

Tags: Programming Database Java JDBC

Posted on Sat, 20 Jun 2020 03:06:27 -0400 by TechMistress