Java Design Patterns - Three Factory Patterns

Posted by dudejma on Sun, 06 Feb 2022 19:26:40 +0100

1. Simple Factory Mode

1.1. Basic Introduction

  • Simple factory mode is a creation mode and one of the factory modes. Simple factory mode is a simple factory object that decides which instance of a product class to create. Simple factory mode is the simplest mode to use in the factory mode family.
  • Simple Factory Mode: Defines a class that creates objects that encapsulate the behavior of instantiated objects.
  • Use scenarios: In software development, this pattern is used when we need to create a large number of objects.

1.2. Example demonstration

  • Example: A pizza shop that now offers cheese pizza, pepper pizza.... Now make a meal ordering system using factory mode.

1.3. Case Needs Analysis

  • We assume that every pizza is made in the same way: raw material preparation (different for each pizza), baking, cutting, packaging. Because each pizza has different ingredients, it needs to be made into an abstract class. Then let other types of pizza inherit this class.
  • We then built a factory to produce all kinds of pizza in this store. And this factory depends on all kinds of pizza.
  • Finally, we let the restaurant and the factory rely on ordering.

1.4. Class Diagram Design

1.5, Code Display

  • First, we write out the abstract class of pizza.
public abstract class Pizza {
    public String name;//Pizza Name

    public abstract void prepare();

    public void back() {
        System.out.println(name + "Start baking");
    }

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

    public void box() {
        System.out.println(name + "Start Packaging");
    }
}
  • Then go for all types of pizza. (For convenience, I will write all types of pizza classes together below. If you copy the code, be careful not to copy all of them directly into one class.)
//Cheese pizza
public class Cheese extends Pizza {
    public Cheese() {
        name = "Cheese pizza";
    }
    @Override
    public void prepare() {
        System.out.println("Start preparing" + name);
    }
}
//Pepper Pizza
public class Pepper extends Pizza {
    public Pepper() {
        name = "Pepper Pizza";
    }

    @Override
    public void prepare() {
        System.out.println("Start preparing" + name);
    }
}
  • Once the pizza is ready, we need to set up a factory to produce it.
public class Factory {
//Pizza production by receiving input pizza types
    public Pizza creatPizza(String type) {
		Pizza pizza = null;
        if ("pepper".equals(type)) {
            pizza =  new Pepper();
        }else if ("cheese".equals(type)) {
            pizza = new Cheese();
        }

        return pizza;
    }
}
  • Finally, order
public class OrderPizza {

    public void order(Factory factory) {
        String type = null;
        Pizza pizza = null;

        do{
            type = getType();
            pizza = factory.creatPizza(type);
//If null is returned, the factory cannot produce the input type of pizza, or the type of pizza does not exist.
            if (null == pizza) {
                System.out.println("No pizza of this type");
                break;
            }

            pizza.prepare();
            pizza.back();
            pizza.cut();
            pizza.box();
        }while(true);
    }
//It is important to note that this method is used to obtain the type of input pizza.
    public String getType() {
        String type = null;

        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("Input Pizza Type:");
            type = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return type;
    }
}
  • Finally, we can start eating pizza.
public class Test {
    public static void main(String[] args) {
        new OrderPizza().order(new Factory());
    }
}

II. Factory Method Mode

2.1. Basic Introduction

  • Factory method mode: Defines an abstract method for creating objects, where subclasses determine which classes to instantiate. Factory method mode postpones the instantiation of objects to subclasses.

2.2. Example demonstration

  • Add a new requirement based on the previous project. Customers can order pizza in different regional flavors, such as cheese pizza from Beijing and cheese pizza from London. They are all cheese pizzas, but they taste different from region to region.

2.3. Case Analysis

  • Some people might say that this simple factory model won't solve the problem. However, if you do that, there will be a problem, class explosion. Think about how many different kinds of pizzas the world wants, and how many conditions inside the factory tell you to write. It's scary when you think about it. This will cause problems for the maintenance and scalability of the later software.
  • Solution: abstract the instantiation function of a pizza project into an abstract method and implement it in different tasting meal classes.

2.4. Class Diagram Design

2.5, Code Display

  • Pizza abstract class is the same as above, so it's not written here anymore.
  • Pizza varieties of different regional flavors,
public class BJCheese extends Pizza {
    public BJCheese() {
        name = "Beijing cheese pizza";
    }
    @Override
    public void prepare() {
        System.out.println("Start preparing" + name);
    }
}

public class BJPepper extends Pizza {
    public BJPepper() {
        name = "Beijing pepper pizza";
    }

    @Override
    public void prepare() {
        System.out.println("Start preparing" + name);
    }
}

public class LDCheese extends Pizza {
    public LDCheese() {
        name = "London cheese pizza";
    }
    @Override
    public void prepare() {
        System.out.println("Start preparing" + name);
    }
}

public class LDPepper extends Pizza {
    public LDPepper() {
        name = "London pepper pizza";
    }

    @Override
    public void prepare() {
        System.out.println("Start preparing" + name);
    }
}
  • Abstract factory
public abstract class Factory {
    public abstract Pizza creatPizza(String type);

    public void order() {
        String type = null;
        Pizza pizza = null;
        while (true) {
            type = getType();
            pizza = creatPizza(type);

            if (null == pizza) {
                System.out.println("No such pizza");
                break;
            }
            pizza.prepare();
            pizza.back();
            pizza.cut();
            pizza.box();
        }
    }

    private String getType() {
        String type = null;

        try {
            BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("Please enter the type of pizza:");
            type = bf.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return type;
    }
}
  • Specific factories
//Production Stores in Beijing
public class BJStory extends Factory {
    @Override
    public Pizza creatPizza(String type) {
        Pizza pizza = null;

        if ("cheese".equals(type)) {
            pizza = new BJCheese();
        }else if("pepper".equals(type)) {
            pizza = new BJPepper();
        }

        return pizza;
    }
}

//Production Shop in London
public class LDStore extends Factory {
    @Override
    public Pizza creatPizza(String type) {
        Pizza pizza = null;
        if ("pepper".equals(type)) {
            pizza = new LDPepper();
        }else if ("cheese".equals(type)) {
            pizza = new LDCheese();
        }

        return pizza;
    }
  • Start ordering. Whether the primary function is simple or not is the benefit of using design patterns.
public class Test {
    public static void main(String[] args) {
        new BJStory().order();
    }
}

3. Abstract Factory Mode

3.1. Basic Introduction

  • An interface is defined to create a cluster of related or dependent objects without specifying a specific class.
  • From the design level, abstract factory mode is a change to simple factory mode
  • The factory is abstracted into two layers, an abstract factory and a concrete implementation of the factory subclass. Programmers can use the corresponding factory subclass based on the type of object they create. This turns a single factory into a cluster of factories, which is more conducive to code maintenance and expansion.

3.2. Example demonstration

  • Like the pattern two example, you are no longer writing.

3.3. Class Diagram Design

3.4. Code display

  • The Pizza class, along with other pizza flavors, is the same as pattern two and is no longer written.
  • Factory Interface
public interface Factory {
    public Pizza creat(String type);
}
  • Specific factories
public class BJFactory implements Factory {
    @Override
    public Pizza creat(String type) {
        Pizza pizza = null;

        if ("cheese".equals(type)) {
            pizza = new BJCheese();
        }else if ("pepper".equals(type)) {
            pizza = new BJPepper();
        }
        return pizza;
    }
}

public class LDFactory implements Factory {
    @Override
    public Pizza creat(String type) {
        Pizza pizza = null;

        if ("pepper".equals(type)) {
            pizza = new LDPepper();
        }else if ("cheese".equals(type)) {
            pizza = new LDCheese();
        }
        return pizza;
    }
}
  • Dinner class, another LDOrder class, like this one, is no longer written. Or you can optimize to save a lot of code writing by putting the code into a class and letting specific factories inherit it.
public class BJOrder {

    public void order(Factory factory) {
        String type = null;
        Pizza pizza = null;

        while (true) {
            type = getTYpe();
            pizza = factory.creat(type);

            if (null == pizza) {
                System.out.println("No such pizza");
                break;
            }

            pizza.prepare();
            pizza.back();
            pizza.cut();
            pizza.box();
        }
    }

    private String getTYpe() {
        String type = null;

        try {
            BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("Please enter the type of pizza:");
            type = bf.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return type;
    }
}
  • Start ordering
public class Store {
    public static void main(String[] args) {
        Factory factory = new BJFactory();

        new BJOrder().order(factory);
    }
}

4. Summary

4.1. Significance of Factory Mode

  • Put the code of the instantiated object in a class for unified management and maintenance to decouple the dependencies of the main project, thereby improving the expansion and maintenance of the project.

4.2. Dependent abstraction principles of design patterns

  • When creating an object instance, instead of directly creating the new class, put the functions of the new class into a factory's method and return.
  • Don't let classes inherit specific classes, but abstract or implement interfaces.
  • Do not override methods already implemented in the class.

Topics: Java Back-end