Explain 23 design patterns (based on Java) - creator pattern (II / V)

Posted by phpr0ck5 on Fri, 12 Nov 2021 17:40:23 +0100

The notes of this article are from the dark horse video https://www.bilibili.com/video/BV1Np4y1z7BU , relevant information can be obtained in the comments area.

2. Creator mode (5)

The main focus of creation mode is "how to create objects?", and its main feature is "separating the creation and use of objects". This can reduce the coupling degree of the system, and users do not need to pay attention to the creation details of objects. There are five kinds of creation modes: Singleton mode, factory method mode, abstract engineering mode, prototype mode and builder mode.

2.1. Singleton mode

Singleton Pattern is one of the simplest design patterns in Java. It provides the best way to create objects. This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique object, which can be accessed directly without instantiating the object of this class.

2.1.1. Structure

The main roles of singleton mode are singleton class and access class:

Singleton classOnly one instance of a class can be created
Access classUsing singleton classes

2.1.2. Realization

There are two types of singleton patterns: hungry and lazy.

Hungry Han styleClass loading will cause the single instance object to be created
Lazy styleClass loading does not cause the single instance object to be created, but only when the object is used for the first time

(1) Hungry Han style
① Method 1: static variable

package com.itheima.patterns.singleton.hungryman1;

//Hungry Han style
public class Singleton{
    
    //Private construction method
    private Singleton(){}
    
    //Static variables create objects of classes
    private static Singleton instance = new Singleton();

    //Provide a static method to get the object
    public static Singleton getInstance(){
        return instance;
    }
}

Description: Method 1 declares a static variable of Singleton type at the member position, and creates an instance object of Singleton class. The instance object is created as the class loads. If the object is large enough and has not been used, it will cause a waste of memory.

② Mode 2: static code block

package com.itheima.patterns.singleton.hungryman2;

public class Singleton{
    
    //Private construction method
    private Singleton(){}
    
    //Create in static code blocks
    private static Singleton instance;
    static {
        instance = new Singleton();
    }
    
    //Provide a static method to get the object
    public static Singleton getInstance(){
        return instance;
    }
}

Note: Method 2: declare static variables of Singleton type at the member position, and the object is created in the static code block and with the loading of the class. Therefore, as in mode 1, there is also a problem of memory waste.

③ Method 3: enumeration method

package com.itheima.patterns.singleton.hungryman3;

public enum Singleton {
    INSTANCE;
}

Note: the enumeration class implementation singleton mode is the highly recommended singleton implementation mode, because the enumeration type is thread safe and can only be loaded once. The designer makes full use of this feature of enumeration to implement the singleton mode. The writing method of enumeration is very simple, and the enumeration type is the only singleton implementation mode that will not be destroyed.
The test code is as follows:
Client.java

package com.itheima.patterns.singleton.hungryman;

public class Client {
    
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        //instance1 and instance2 are the same object, and the result is true
        System.out.println(instance1 == instance2);  
    }
}

(2) Lazy style
① Mode 1: synchronized thread safety

package com.itheima.patterns.singleton.Lazyman;

public class Singleton{
    
    //Private construction method
    private Singleton(){}
    
    //Declare the variable instance of Singleton type without assignment
    private static Singleton instance;
    
    //The keyword synchronized is used to ensure thread safety
    public static synchronized Singleton getInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

Note: this method not only realizes the lazy loading effect, but also solves the thread safety problem. However, the synchronized keyword is added to the getInstance() method, resulting in a particularly low execution effect of the method. From the above code, we can see that the thread safety problem only occurs when initializing instance. Once the initialization is completed, it does not exist.

② Mode 2: double check lock

package com.itheima.patterns.singleton.lazyman2;

public class Singleton {
    
    //Private construction method
    private Singleton(){}
    
    //Declare the variable instance of Singleton type without assignment
    private static volatile Singleton instance;
    
    public static Singleton getInstance(){
        //In the first check, if instance is not null, the lock grabbing phase will not be entered, and the actual value can be returned directly
        if(instance == null){
            synchronized(Singleton.class){
                //The second time, after obtaining the lock, judge whether instance is empty again
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Note: the double check lock mode is a very good single instance implementation mode, which solves the problems of single instance, performance and thread safety. Although the double check lock mode looks perfect, it is actually a problem. In the case of multiple threads, there may be a null pointer problem, The reason for the problem is that the JVM performs optimization and instruction reordering operations when instantiating objects. To solve the problem of null pointer exception caused by double check lock mode, you only need to use volatile keyword, which can ensure visibility and order. The double check lock mode after adding volatile keyword is a better singleton implementation mode, which can ensure thread safety and no performance problems in the case of multithreading.

③ Method 3: static internal class method

package com.itheima.patterns.singleton.lazyman3;

public class Singleton {
    
    //Private construction method
    private Singleton(){}
    
    //Static inner class
    private static class SingletonHolder{
        private static final Singleton INSTANCE = new Singleton();
    }
    
    //Provide a static method to get the object
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

Note: the INSTANCE will not be initialized when the Singleton class is loaded for the first time. Only when getInstance() is called for the first time, the virtual machine loads the SingletonHolder and initializes the INSTANCE, which can not only ensure thread safety, but also ensure the uniqueness of the Singleton class. In short, the static inner class Singleton pattern is an excellent Singleton pattern, which is commonly used in open source projects. Without any lock, it ensures the safety of multithreading without any performance impact and waste of space.

2.1.3. Existing problems

Destroy singleton mode demonstration (serialization, deserialization and reflection):
(1) Serialization and deserialization

package com.itheima.patterns.singleton.problem1;

import java.io.Serializable;

public class Singleton implements Serializable {
    //Private construction method
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    //Provide a static method to get the object
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
package com.itheima.patterns.singleton.problem1;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;


public class Client {
    public static void main(String[] args) throws Exception {
        writeObject2File();
        readObjectFromFile();
        readObjectFromFile();
    }
    
    //Read data from file (object)
    public static void readObjectFromFile() throws Exception {
        //1. Create an input stream object
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\testData\\a.txt"));
        //2. Read object
        Singleton instance = (Singleton) ois.readObject();        
        System.out.println(instance);        
        //Release resources
        ois.close();
    }
    
    //Write data (objects) to a file
    public static void writeObject2File() throws Exception {
        //1. Get Singleton object
        Singleton instance = Singleton.getInstance();
        //2. Create an output stream object
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\testData\\a.txt"));
        //3. Write object
        oos.writeObject(instance);
        //4. Release resources
        oos.close();
    }
}


(2) Reflection

public class Singleton {
	//Private construction method
	private Singleton() {}
	private static volatile Singleton instance;
	//Provide a static method to get the object
	public static Singleton getInstance() {
		if(instance != null) {
			return instance;
		}
		synchronized (Singleton.class) {
			if(instance != null) {
				return instance;
			}
			instance = new Singleton();
			return instance;
		}
	}
}
package com.itheima.patterns.singleton.problem2;

import java.lang.reflect.Constructor;

public class Client {
    public static void main(String[] args) throws Exception {
        //Gets the bytecode object of the Singleton class
        Class clazz = Singleton.class;
        //Gets the private parameterless constructor object of the Singleton class
        Constructor constructor = clazz.getDeclaredConstructor();
        //Cancel access check
        constructor.setAccessible(true);
        //Create object s1 of Singleton class
        Singleton s1 = (Singleton) constructor.newInstance();
        //Create an object of the Singleton class s2
        Singleton s2 = (Singleton) constructor.newInstance();
        //Determine whether two Singleton objects created by reflection are the same object
        System.out.println(s1 == s2);  //false
    }
}

2.1.4. Problem solving

(1) Solution of destroying singleton pattern by serialization and deserialization
Add the readResolve() method to the Singleton class. It is called by reflection during deserialization. If this method is defined, it returns the value of this method. If it is not defined, it returns the new object.

package com.itheima.patterns.singleton.reslove1;

import java.io.Serializable;

public class Singleton implements Serializable {
    //Private construction method
    private Singleton() {}
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    //Provide a static method to get the object
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    //Solve serialization and deserialization to crack singleton mode
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

Specific in-depth analysis can be referred to This article.
(2) Solution of cracking single case in reflection mode
When the constructor is called for creation through reflection, the exception is thrown directly and the operation in this step is not run.

package com.itheima.patterns.singleton.reslove1;

import java.io.Serializable;

public class Singleton implements Serializable {
    
    private static boolean flag = false;
    
    //Private construction method
    private Singleton() {
        synchronized (Singleton.class){
            //If the value of flag is true, it indicates that it is not the first time to access, and an exception is thrown directly
            if(flag){
                throw new RuntimeException("Cannot create multiple objects!");
            }
            flag = true;
        }
    }
    
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    //Provide a static method to get the object
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    
    //Solve serialization and deserialization to crack singleton mode
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
}

2.1.5.JDK source code analysis - Runtime class

(1) The Runtime class is the singleton design pattern used. Part of its source code is as follows:

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
	...
}

From the above source code, we can see that the Runtime class uses starving Han style (static attribute) to implement the singleton pattern.
(2) Simply use the Runtime class

package com.itheima.patterns.singleton.runtimedemo;

import java.io.IOException;
import java.io.InputStream;

public class RunTimeDemo {
    
    public static void main(String[] args) throws IOException {
        //Get RunTime class object
        Runtime runtime = Runtime.getRuntime();
    
        System.out.println("JVM idle memory=" + runtime.freeMemory()/(1024*1024) + "M");
        System.out.println("JVM Total memory=" + runtime.totalMemory()/(1024*1024) + "M");
        System.out.println("JVM Maximum available memory=" + runtime.maxMemory()/(1024*1024) + "M");
        
        //Call the method exec of runtime, and the parameter is a command
        Process process = runtime.exec("ipconfig");
        //Call the method of the process object to get the input stream
        InputStream is = process.getInputStream();
        byte[] arr = new byte[1024 * 1024 * 100];
        int length = is.read(arr);
        System.out.println(new String(arr,0,length,"GBK"));
    }
}

2.2. Factory method mode

[requirements] design a coffee shop ordering system
Design a Coffee category and define its two subclasses (American Coffee and latte Coffee); Then design a Coffee store, which has the function of ordering Coffee. The design of specific classes is as follows:

In Java, everything is an object, and these objects need to be created. If you directly new the object when creating it, the object will be seriously coupled. If you want to replace the object, all new objects need to be modified, which obviously violates the opening and closing principle of software design. If we use a factory to produce objects, we can only deal with the factory and completely decouple from the object. If we want to replace the object, we can directly replace the object in the factory, so as to achieve the purpose of decoupling from the object; So the biggest advantage of factory mode is decoupling.
The following describes three factory patterns: simple factory pattern, factory method pattern and abstract factory pattern:

2.2.1. Simple factory mode

Simple factory is not a design pattern, but more like a programming habit.
(1) Structure
The simple factory contains the following roles:
① Abstract product: it defines the product specification and describes the main characteristics and functions of the product.
② Concrete products: subclasses that implement or inherit Abstract products.
③ Specific factory: provides a method to create a product, through which the caller obtains the product.
(2) Realize
Now use a simple factory to improve the above case. The class diagram is as follows:

The implementation code is as follows:
Coffee.java

package com.itheima.patterns.factory.simple_factory;

public abstract class Coffee {
    
    public abstract String getName();
    
    //Add sugar
    public void addSugar(){
        System.out.println("Add sugar");
    }
    
    //Add milk
    public void addMilk(){
        System.out.println("Add milk");
    }
}

AmericanCoffee.java

package com.itheima.patterns.factory.simple_factory;

//Cafe Americano
public class AmericanCoffee extends Coffee {
    @Override
    public String getName() {
        return "Cafe Americano";
    }
}

LatteCoffee.java

package com.itheima.patterns.factory.simple_factory;

//Cafe Latte
public class LatteCoffee extends Coffee {
    
    @Override
    public String getName() {
        return "Cafe Latte";
    }
}

SimpleCoffeeFactory.java

package com.itheima.patterns.factory.simple_factory;

public class SimpleCoffeeFactory {
    
    public Coffee createCoffee(String type) {
        //Declare variables of coffee type and create different coffee subclass objects according to different types
        Coffee coffee = null;
        if("american".equals(type)) {
            coffee = new AmericanCoffee();
        } else if("latte".equals(type)) {
            coffee = new LatteCoffee();
        } else {
            throw new RuntimeException("Sorry, there is no coffee you ordered");
        }
        return coffee;
    }
}

CoffeeStore.java

package com.itheima.patterns.factory.simple_factory;

public class CoffeeStore {
    public Coffee orderCoffee(String type){
        SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
        //Call the method of producing coffee
        Coffee coffee = factory.createCoffee(type);
    
        //Add ingredients
        coffee.addMilk();
        coffee.addSugar();
    
        return coffee;
    }
}

Clien.java

package com.itheima.patterns.factory.simple_factory;

public class Clien {
    public static void main(String[] args) {
        //1. Create a coffee shop class
        CoffeeStore store = new CoffeeStore();
        //2. Order coffee
        Coffee coffee = store.orderCoffee("american");
        System.out.println(coffee.getName());
    }
}

The factory handles the details of creating objects. Once SimpleCoffeeFactory is available, orderCoffee() in the CoffeeStore class becomes the customer of this object. Later, if you need to obtain the Coffee object directly from the factory. In this way, the coupling with the Coffee implementation class is released, and new coupling is generated, including the coupling between the Coffee store object and the SimpleCoffeeFactory factory object, and the coupling between the factory object and the commodity object. If new varieties of Coffee are added later, we need to modify the code of SimpleCoffeeFactory, which violates the opening and closing principle. There may be many factory class clients, such as creating meituan takeout. In this way, you only need to modify the factory class code and save other modification operations.
(3) Advantages and disadvantages:
Advantages: encapsulates the process of creating objects, and objects can be obtained directly through parameters. Separate the object creation from the business logic layer, so as to avoid modifying the customer code in the future. If you want to implement a new product, you can directly modify the factory class without modifying it in the original code, which reduces the possibility of modifying the customer code and makes it easier to expand.
Disadvantages: when adding new products, you still need to modify the factory code, which violates the "opening and closing principle".
(4) Expand
Static factory: during development, some people define the function of creating objects in the factory class as static. This is the static factory pattern, and it is not
Of the 23 design patterns. The code is as follows:

public class SimpleCoffeeFactory {
	public static Coffee createCoffee(String type) {
		Coffee coffee = null;
		if("americano".equals(type)) {
			coffee = new AmericanoCoffee();
		} else if("latte".equals(type)) {
			coffee = new LatteCoffee();
		}
		return coffe;
	}
}

2.2.2. Factory method mode

In view of the shortcomings in the above example, the factory method mode can be used to solve them perfectly and fully follow the opening and closing principle. That is, define an interface for creating objects, and let subclasses decide which product class object to instantiate. Factory methods delay instantiation of a product class to subclasses of its factory.
(1) Structure
The main roles of the factory approach model are:
① Abstract Factory: it provides an interface for creating products, through which callers access factory methods of specific factories to create products.
② Concrete factory: it mainly implements the abstract methods in the abstract factory and completes the creation of specific products.
③ Abstract Product: it defines the specification of the Product and describes the main characteristics and functions of the Product.
④ Concrete product: it implements the interface defined by the abstract product role, which is created by the specific factory and corresponds to the specific factory one by one.
(2) Realize
The factory method mode is used to improve the above example. The class diagram is as follows:

Some codes are as follows:
CoffeeFactory.java

package com.itheima.patterns.factory.factory_method;

//Abstract factory provides an interface for creating products, through which callers access factory methods of specific factories to create products
public interface CoffeeFactory {
    //Produce coffee
    Coffee createCoffee();
}

AmericanCoffeeFactory.java

package com.itheima.patterns.factory.factory_method;

//Specific factory: producing American coffee
public class AmericanCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}

LatteCoffeeFactory.java

package com.itheima.patterns.factory.factory_method;

//Specific factory: producing Latte Coffee
public class LatteCoffeeFactory implements CoffeeFactory{
    
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}

CoffeeFactory.java

package com.itheima.patterns.factory.factory_method;

//coffee shop
public class CoffeeStore {
    
    //Create a coffee factory
    private CoffeeFactory factory;
    
    public void setFactory(CoffeeFactory factory) {
        this.factory = factory;
    }
    
    //Order coffee function
    public Coffee orderCoffee() {
        Coffee coffee = factory.createCoffee();
        //Add ingredients
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

Client.java

package com.itheima.patterns.factory.factory_method;

public class Client {
    public static void main(String[] args) {
        //Create coffee shop object
        CoffeeStore coffeeStore = new CoffeeStore();
        //Create a specific coffee shop factory
        CoffeeFactory factory = new AmericanCoffeeFactory();
        //CoffeeFactory factory = new LatteCoffeeFactory();
        coffeeStore.setFactory(factory);
        //Order coffee
        Coffee coffee = coffeeStore.orderCoffee();
        System.out.println(coffee.getName());
    }
}

From the code written above, we can see that when adding product classes, we should also add factory classes accordingly. There is no need to modify the code of factory classes, which solves the shortcomings of simple factory mode. The factory method pattern is a further abstraction of the simple factory pattern. Due to the use of polymorphism, the factory method pattern not only maintains the advantages of the simple factory pattern, but also overcomes its disadvantages.
(3) Advantages and disadvantages of factory method mode:
advantage:
① Users only need to know the name of the specific factory to get the desired product, without knowing the specific creation process of the product;
② When adding new products to the system, only the specific product category and the corresponding specific factory category need to be added without any modification to the original factory, which meets the opening and closing principle;
Disadvantages: every time a product is added, a specific product class and a corresponding specific factory class must be added, which increases the complexity of the system.

2.3. Abstract factory mode

In the factory method mode introduced earlier, the production of a class of products is considered, such as only raising animals in livestock ranch, and only producing televisions in TV factory. These factories only produce the same kind of products, and the same kind of products are called products of the same grade, that is, the factory method mode only considers the production of products of the same grade, but in real life, many factories are comprehensive factories that can produce multi-level (type) products, such as electric appliance factories that produce both TV sets and washing machines or air conditioners, The university has both software and biology majors.
The abstract factory mode to be introduced in this section will consider the production of multi-level products. A group of products at different levels produced by the same specific factory is called a product family. The horizontal axis shown in the figure below is the product level, that is, the same type of products; The vertical axis is the product family, that is, the products of the same brand, and the products of the same brand are produced in the same factory.

2.3.1. Concept

Abstract factory pattern is a pattern structure that provides an interface for an access class to create a group of related or interdependent objects, and the access class can obtain different levels of products of the same family without specifying the specific class of the product. Abstract factory pattern is an upgraded version of factory method pattern. Factory method pattern only produces one level of products, while abstract factory pattern can produce multiple levels of products.

2.3.2. Structure

The main roles of the abstract factory pattern are as follows:
① Abstract Factory: it provides an interface for creating products. It contains multiple methods for creating products, which can
Create multiple products of different levels.
② Concrete Factory: it mainly implements multiple abstract methods in the abstract factory to complete the creation of specific products.
③ Abstract Product: it defines the Product specification and describes the main features and functions of the Product. The abstract factory pattern has multiple Abstract products.
④ Concrete product: it implements the interface defined by the abstract product role and is created by the concrete factory. It has a many-to-one relationship with the concrete factory.

2.3.3. Realization

At present, the business of coffee shops has changed, not only to produce coffee, but also to produce desserts, such as tiramisu and Matcha mousse. If you need to define tiramisu, Matcha mousse, tiramisu factory, Matcha mousse factory and dessert factory according to the factory method mode, it is easy to explode. Latte and American coffee are one product grade, both of which are coffee; Tiramisu and Matcha mousse are also a product grade; Latte and tiramisu belong to the same product family (i.e. both belong to Italian flavor), American coffee and Matcha mousse belong to the same product family (i.e. both belong to American flavor). Therefore, this case can be implemented using the abstract factory pattern. Class diagram is as follows:

Some core codes are as follows:
DessertFactory.java

package com.itheima.patterns.factory.abstract_factory;

//Abstract factory class
public interface DessertFactory {
    //Function of producing coffee
    Coffee createCoffee();
    
    //Function of producing dessert
    Dessert createDessert();
}

AmericanDessertFactory.java

package com.itheima.patterns.factory.abstract_factory;

//American flavor dessert factory can produce American coffee and Matcha mousse
public class AmericanDessertFactory implements DessertFactory{
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
    
    @Override
    public Dessert createDessert() {
        return new Matchamousse();
    }
}

ItalyDessertFactory.java

package com.itheima.patterns.factory.abstract_factory;

//Italian flavor dessert factory, which can produce latte coffee and tiramisu dessert
public class ItalyDessertFactory implements DessertFactory{
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
    
    @Override
    public Dessert createDessert() {
        return new Trimisu();
    }
}

Client.java

package com.itheima.patterns.factory.abstract_factory;

public class Client {
    public static void main(String[] args) {
        //Created is an Italian dessert factory object
        ItalyDessertFactory factory = new ItalyDessertFactory();
        //Get latte and tiramisu desserts
        Coffee coffee = factory.createCoffee();
        Dessert dessert = factory.createDessert();
    
        System.out.println(coffee.getName());
        dessert.show();
    }
}

If you want to add the same product family, you only need to add another corresponding factory class without modifying other classes.

2.3.4. Advantages and disadvantages

Advantages: when multiple objects in a product family are designed to work together, it can ensure that the client always uses only the objects in the same product family.
Disadvantages: when a new product needs to be added to the product family, all factory classes need to be modified.

2.3.5. Usage scenario

① When the objects to be created are a series of interrelated or interdependent product families, such as televisions, washing machines, air conditioners, etc.
② There are multiple product families in the system, but only one of them is used at a time. If someone only likes to wear clothes and shoes of a certain brand.
③ The class library of products is provided in the system, and the interfaces of all products are the same. The client does not depend on the creation details and internal structure of product instances.

2.3.6. Mode extension

(1) You can decouple factory objects and product objects by means of factory mode + configuration file. Load the full in the configuration file in the factory class
Class name, and create an object for storage. If the client needs an object, it can be obtained directly.
(2) The specific steps are as follows:
① Define the configuration file bean.properties

american=com.itheima.patterns.factory.config_factory.AmericanCoffee
latte=com.itheima.patterns.factory.config_factory.LatteCoffee

② Improved factory

package com.itheima.patterns.factory.config_factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Properties;
import java.util.Set;

public class CoffeeFactory {
    
    //Load the configuration file, obtain the full class name configured in the configuration file, and create the object of this class for storage
    //1. Define container objects to store coffee objects
    private static HashMap<String,Coffee> map = new HashMap<String, Coffee>();
    
    //2. Load the configuration file only once
    static {
        //2.1. Create Properties object
        Properties properties = new Properties();
        //2.2. Call the load method in the properties object to load the configuration file
        InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            properties.load(is);
            //Get the full class name from the properties collection and create the object
            Set<Object> keys = properties.keySet();
            for (Object key : keys) {
                String className = properties.getProperty((String) key);
                //Create objects using reflection techniques
                Class clazz = Class.forName(className);
                Coffee coffee = (Coffee) clazz.newInstance();
                //Store names and objects in containers
                map.put((String) key, coffee);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    //Get object by name
    public static Coffee createCoffee(String name) {
        return map.get(name);
    }
}

③ Testing

package com.itheima.patterns.factory.config_factory;

public class Client {
    public static void main(String[] args) {
        Coffee coffee1 = CoffeeFactory.createCoffee("american");
        System.out.println(coffee1.getName());    //Cafe Americano
        Coffee coffee2 = CoffeeFactory.createCoffee("latte");
        System.out.println(coffee2.getName());    //Cafe Latte
    }
}

The static member variable is used to store the created object (the key stores the name and the value stores the corresponding object), while reading the configuration file and creating the object are written in the static code block, so that it only needs to be executed once.

2.3.7.JDK source code analysis - Collection.iterator method

(1) Let's take a look at this code first

package com.itheima.patterns.factory.iteratordemo;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Tom");
        list.add("Mike");
        list.add("Marry");
    
        //Get iterator object
        Iterator<String> iterator = list.iterator();
        //Traversal using iterators
        while(iterator.hasNext()){
            String element = iterator.next();
            System.out.println(element);
        }
    }
}

You should be familiar with the above code, which uses iterators to traverse the collection and get the elements in the collection. The factory method pattern is used in the method of obtaining iterators from single column sets. Now look at the structure through the class diagram:

The Collection interface is an abstract factory class, and ArrayList is a concrete factory class; The Iterator interface is an abstract commodity class, and the Iter inner class in the ArrayList class is a concrete commodity class. In the concrete factory class, the iterator() method creates the object of the concrete commodity class.
Iterator.java

Collection.java

ArrayList.java


Note: getInstance() method in DateForamt class and getInstance() method in Calendar class also use factory mode.

2.4. Prototype mode

2.4.1. General

(1) Prototype mode: use an instance that has been created as a prototype, and create a new object that is the same as the prototype object by copying the prototype object.
(2) Usage scenario:
① When the object to be created is very complex, you can use the prototype pattern to create it quickly.
② Prototype mode can also be used when performance and security requirements are high.

2.4.2. Structure

(1) The prototype pattern contains the following roles:
① Abstract prototype class: Specifies the clone() method that the concrete prototype object must implement.
② Concrete prototype class: implement the clone() method of the Abstract prototype class, which is an object that can be copied.
③ Access class: use the clone() method in the concrete prototype class to copy the new object.
(2) The interface class diagram is as follows:

2.4.3. Realization

(1) The cloning of prototype pattern can be divided into shallow cloning and deep cloning.
① Shallow cloning: create a new object. The properties of the new object are exactly the same as those of the original object. For non basic type properties, it still points to the memory address of the object pointed to by the original property.
② Deep clone: when a new object is created, other objects referenced in the attribute will also be cloned and no longer point to the original object address.
(2) The clone() method is provided in the Object class in Java to implement shallow cloning. Cloneable interface is the Abstract prototype class in the above class diagram, and the sub implementation class that implements Cloneable interface is the specific prototype class.

2.4.3.1. Shallow cloning

Address.java

package com.itheima.patterns.prototype.shallowcopy;

public class Address {
    int id;
    String addressName;
    
    public Address(int id, String addressName) {
        this.id = id;
        this.addressName = addressName;
    }
}

Person.java

package com.itheima.patterns.prototype.shallowcopy;

public class Person implements Cloneable{
    private int id;
    private String name;
    private Address address;
    
    public String getName() {
        return name;
    }
    
    public Person(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
        System.out.println("The specific prototype object is created!");
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("Specific prototype copied successfully!");
        return (Person)super.clone();
    }
    
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address=" + address +
                '}';
    }
    
}

Client.java

package com.itheima.patterns.prototype.shallowcopy;

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person(1,"Xiaohua",new Address(1,"Beijing"));
        Person person2 = (Person) person1.clone();
        System.out.println("person1:"+person1);
        System.out.println("person2:"+person2);
        System.out.println("person1.getName() == person2.getName()The results are:"+(person1.getName() == person2.getName()));
        System.out.println("person1 == person2 The results are:"+(person1 == person2));
    }
}

2.4.3.2. Deep cloning

(1) Deep copy through object serialization (recommended)
First implement the Serializable interface for the Address class and Person class, and then modify the code in Client.java.
Client.java

package com.itheima.patterns.prototype.deepcopy;

import java.io.*;

public class Client {
    public static void main(String[] args) throws Exception {
        Person person1 = new Person(1,"Xiaohua",new Address(1,"Beijing"));
    
        //Create object output stream object
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\\testData\\a.txt"));
        //Write object
        oos.writeObject(person1);
        //Release resources
        oos.close();
    
        //Create an input stream object
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\\testData\\a.txt"));
        //read object
        Person person2 = (Person) ois.readObject();
        //Release resources
        ois.close();
        
        System.out.println("person1:"+person1);
        System.out.println("person2:"+person2);
        System.out.println("person1.getName() == person2.getName()The results are:"+(person1.getName() == person2.getName()));
        System.out.println("person1 == person2 The results are:"+(person1 == person2));
    }
}


(2) Override the clone() method to implement a deep copy

package com.itheima.patterns.prototype.deepcopy2;

public class Person implements Cloneable {
    private int id;
    private String name;
    private Address address;
    
    public String getName() {
        return name;
    }
    
    public Person(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
        System.out.println("The specific prototype object is created!");
    }
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        System.out.println("Specific prototype copied successfully!");
        Person person = null;
        person = (Person)super.clone();
        //Reference data types are processed separately
        person.name = new String(name);
        person.address = (Address)address.clone();
        return person;
    }
    
    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address=" + address +
                '}';
    }
}

2.5. Builder mode

2.5.1. General

Builder pattern: separate the construction and representation of a complex object, so that the same construction process can create different representations.
(1) It separates the construction of components (in the charge of Builder) and assembly (in the charge of Director). Thus, complex objects can be constructed. This pattern is applicable to the complex construction process of an object.
(2) The decoupling of construction and assembly is realized. Different builders and the same assembly can also make different objects; With the same builder, different assembly sequences can also make different objects. That is to realize the decoupling of construction algorithm and assembly algorithm, and realize better reuse.
(3) The builder pattern can separate the component from its assembly process and create a complex object step by step. Users only need to specify the type of complex object to get the object without knowing its internal specific construction details.

2.5.2. Structure

The builder mode includes the following roles:
(1) Abstract Builder class: this interface specifies the creation of those parts of complex objects, and does not involve the creation of specific component objects.
(2) Concrete Builder class: it implements the Builder interface and completes the specific creation method of various components of complex products. After the construction process is completed, an example of the product is provided.
(3) Product: complex object to create.
(4) Director: call the specific builder to create each part of the complex object. The director does not involve the information of the specific product, but is only responsible for ensuring that each part of the object is created completely or in a certain order.
Class diagram is as follows:

2.5.3. Examples

[example] create a shared bike
The production of bicycle is a complex process, which includes the production of frame, seat and other components. The frame is made of carbon fiber, aluminum alloy and other materials, and the seat is made of rubber, leather and other materials. For the production of bicycles, the builder model can be used. Here Bike is a product, including frame, seat and other components; Builder is an abstract builder, MobikeBuilder and OfoBuilder are concrete builders; Director is the commander. Class diagram is as follows:

The specific codes are as follows:
Bike.java

package com.itheima.patterns.builder.demo1;

public class Bike {
    private String frame;//Frame
    
    private String seat;//Car seat
    
    public String getFrame() {
        return frame;
    }
    
    public void setFrame(String frame) {
        this.frame = frame;
    }
    
    public String getSeat() {
        return seat;
    }
    
    public void setSeat(String seat) {
        this.seat = seat;
    }
}

Builder.java

package com.itheima.patterns.builder.demo1;

public abstract class Builder {
    //Declare variables of type Bike and assign values
    protected Bike bike = new Bike();
    
    public abstract void buildFrame();
    
    public abstract void buildSeat();
    
    //Method of constructing bicycle
    public abstract Bike createBike();
}

MobileBuilder.java

package com.itheima.patterns.builder.demo1;

//The specific builder used to build the Moby bike object
public class MobileBuilder extends Builder{
    public void buildFrame() {
        bike.setFrame("Carbon fiber frame");
    }
    
    public void buildSeat() {
        bike.setSeat("Leather seat");
    }
    
    public Bike createBike() {
        return bike;
    }
}

OfoBuilder.java

package com.itheima.patterns.builder.demo1;

public class OfoBuilder extends Builder{
    public void buildFrame() {
        bike.setFrame("Aluminum alloy frame");
    }
    
    public void buildSeat() {
        bike.setSeat("Rubber seat");
    }
    
    public Bike createBike() {
        return bike;
    }
}

Director.java

package com.itheima.patterns.builder.demo1;

public class Director {
    //Declare variables of type builder
    private Builder builder;
    
    public Director(Builder builder) {
        this.builder = builder;
    }
    
    //Function of assembling bicycle
    public Bike construct() {
        builder.buildFrame();
        builder.buildSeat();
        return builder.createBike();
    }
}

Client.java

package com.itheima.patterns.builder.demo1;

public class Client {
    public static void main(String[] args) {
        //Create commander object
        Director director = new Director(new MobileBuilder());
        //Let the conductor only assemble bicycles
        Bike bike = director.construct();
        
        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}

Note: the above example is the general usage of the Builder mode. The commander class Director plays a very important role in the Builder mode. It is used to guide the specific Builder how to build the product, control the call order, and return the complete product class to the caller. However, in some cases, the system structure needs to be simplified, and the commander class can be combined with the abstract Builder.

// Abstract builder class
public abstract class Builder {
	protected Bike mBike = new Bike();
	
	public abstract void buildFrame();
	public abstract void buildSeat();
	public abstract Bike createBike();
	
	public Bike construct() {
		this.buildFrame();
		this.BuildSeat();
		return this.createBike();
	}
}

2.5.4. Advantages and disadvantages

(1) Advantages:
① The builder model is well encapsulated. Using the builder mode can effectively encapsulate changes. In the scenario of using the builder mode, the general product class and builder class are relatively stable. Therefore, encapsulating the main business logic in the commander class can achieve better stability as a whole.
② In the builder mode, the client does not need to know the details of the internal composition of the product, and decouples the product itself from the product creation process, so that the same creation process can create different product objects.
③ You can more finely control the product creation process. The creation steps of complex products are decomposed into different methods, which makes the creation process clearer and easier to use programs to control the creation process.
④ The builder pattern is easy to extend. If there are new requirements, it can be completed by implementing a new builder class. Basically, there is no need to modify the previously tested code, so there will be no risk to the original functions. Comply with the opening and closing principle.
(2) Disadvantages:
The products created by Creator mode generally have more in common and their components are similar. If there are great differences between products, it is not suitable to use builder mode, so its scope of use is limited.

2.5.5. Usage scenario

The Builder pattern creates complex objects. Each part of its product is often faced with drastic changes, but the algorithm for combining them is relatively stable. Therefore, it is usually used in the following occasions:
① The created object is complex and consists of multiple components. Each component faces complex changes, but the construction sequence between components is stable.
② The algorithm of creating a complex object is independent of the components of the object and their assembly mode, that is, the construction process and final representation of the product are independent.

2.5.6. Mode extension

In addition to the above purposes, the builder mode is also commonly used in development. When a class constructor needs to pass in many parameters, if an instance of this class is created, the code readability will be very poor, and it is easy to introduce errors. At this time, the builder mode can be used for reconstruction.
(1) The code before refactoring is as follows:
Phone.java

package com.itheima.patterns.builder.demo2;

public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;
    
    public Phone(String cpu, String screen, String memory, String mainboard) {
        this.cpu = cpu;
        this.screen = screen;
        this.memory = memory;
        this.mainboard = mainboard;
    }
    
    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}

Client.java

package com.itheima.patterns.builder.demo2;

public class Client {
    public static void main(String[] args) {
        //Building Phone objects
        Phone phone = new Phone("unicorn","Samsung screen","Kingston","Asus motherboard");
        System.out.println(phone);
    }
}

The Phone object built in the client code above passes four parameters. If there are more parameters, the readability of the code will become worse and the cost of use will become higher.
(2) The reconstructed code is as follows:
Phone.java

package com.itheima.patterns.builder.demo3;

public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;
    
    private Phone(Builder builder) {
        cpu = builder.cpu;
        screen = builder.screen;
        memory = builder.memory;
        mainboard = builder.mainboard;
    }
    
    public static final class Builder{
        private String cpu;
        private String screen;
        private String memory;
        private String mainboard;
        
        public Builder(){}
        
        public Builder cpu(String val){
            cpu = val;
            return this;
        }
    
        public Builder screen(String val){
            screen = val;
            return this;
        }
    
        public Builder memory(String val){
            memory = val;
            return this;
        }
    
        public Builder mainboard(String val){
            mainboard = val;
            return this;
        }
        
        public Phone build(){
            return new Phone(this);
        }
    }
    
    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}

Client.java

package com.itheima.patterns.builder.demo3;

public class Client {
    public static void main(String[] args) {
        //Building Phone objects
        Phone phone = new Phone.Builder()
                .cpu("unicorn")
                .memory("Kingston")
                .screen("Samsung screen")
                .mainboard("ASUS")
                .build();
        System.out.println(phone);
    }
}

The refactored code is more convenient to use and can improve the development efficiency to some extent. In terms of software design, the requirements for programmers are relatively high.

2.5.7. Creator mode comparison

(1) Factory method mode VS builder mode
The factory method pattern focuses on the creation of the overall object; The builder pattern focuses on the process of component construction, which is intended to create a complex object through precise construction step by step.
(2) Abstract factory pattern VS builder pattern
① Abstract factory mode realizes the creation of product families. A product family is a series of products: product combinations with different classification dimensions. Adopting abstract factory mode does not need to care about the construction process, but only about what products are produced by what factory.
② The builder model requires the product to be built according to the specified blueprint. Its main purpose is to produce a new product by assembling spare parts. If the abstract factory pattern is regarded as an auto parts production factory to produce the products of a product family, the builder pattern is an auto assembly factory, which can return a complete car through the assembly of parts.

Topics: Java Design Pattern