The growth path of Architects - Design Patterns - 07 Prototype mode, clone mode, java attribute replication

Prototype mode

Prototype pattern solves the problem of copying a large number of attributes.
This paper will introduce several depth attribute replication methods and several shallow attribute replication methods.
The source code address of this paper will be given at the end of the text.

Code before prototype pattern is not used

public static void testCloneStupid() {
        PrototypePerson person = new PrototypePerson()
                .setId("10001")
                .setAge(18)
                .setName("First sight")
                .setHobbies(CollUtil.newArrayList("Suckling", "sleep"));

        // Manually copy attributes
        PrototypePerson clonePerson = new PrototypePerson();
        clonePerson.setName(person.getName());
        clonePerson.setAge(person.getAge());
        clonePerson.setId(person.getId());
        // ....  There are many more attributes to copy
        clonePerson.setHobbies(person.getHobbies());

        clonePerson.getHobbies().add("Beat beans");
        System.out.println("person:" + person.toString());
        System.out.println("clonePerson:" + clonePerson.toString());
    }

The code is standardized and neat. What's the problem? Imagine, what if there are 100 + attributes that need to be assigned? The prototype pattern solves the problem caused by such a large number of attribute replication.

Code after prototype mode transformation

public class PrototypePerson implements Cloneable, Serializable {
    private String id;
    private String name;
    private Integer age;
    private List<String> hobbies;
    @Override
    public PrototypePerson clone() {
        try {
            // The clone of jdk must implement Cloneable and serializable interfaces
            return (PrototypePerson) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

Caller usage code

public static void testJdkClone() {
        PrototypePerson person = new PrototypePerson()
                .setId("10001")
                .setAge(18)
                .setName("First sight")
                .setHobbies(CollUtil.newArrayList("Suckling", "sleep"));
        // jdk's own clone
        PrototypePerson clonePerson = person.clone();

        clonePerson.getHobbies().add("Beat beans");
        System.out.println("person:" + person.toString());
        System.out.println("clonePerson:" + clonePerson.toString());
    }

The output is as follows:

Using the prototype pattern greatly simplifies the caller's code. Write the implementation details into the entity class.
However, the problem is that when copying the attributes of types, references are copied, so when modifying person's hobbies, the attributes of clonePerson will be modified synchronously.

Deep and shallow replication

Shallow copy: when copying the attributes of an object type, the reference of the object is copied (for example, the clone of jdk is a shallow copy).
Deep copy: completely copy the properties of the source object, including the properties of the object type.

Byte stream serialization method of deep replication

public static <T> T deepClone(T obj) {
        if (null == obj) return null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            Object o = ois.readObject();
            return (T) o;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

Calling code:

public static void testDeepClone() {
        PrototypePerson person = new PrototypePerson()
                .setId("10001")
                .setAge(18)
                .setName("First sight")
                .setHobbies(CollUtil.newArrayList("Suckling", "sleep"));
        PrototypePerson clonePerson = PrototypeUtils.deepClone(person);

        clonePerson.getHobbies().add("Beat beans");
        System.out.println("person:" + person.toString());
        System.out.println("clonePerson:" + clonePerson.toString());

    }

Output result:

Byte stream serialization and deep replication are commonly used with high performance.

Json serialization method of deep replication

public static <T> T deepCloneByJson(T obj) {
     String jsonString = JSON.toJSONString(obj);
     Object object = JSON.parseObject(jsonString, obj.getClass());
     return (T) object;
 }

Json serialization deep replication method is simple to code, and Xiaobai can remember it~
The test code is not given here, and the output result is the same as above.

Shallow replication of BeanUtils

// Spring's BeanUtils is used here
// import org.springframework.beans.BeanUtils;

public static void testBeanUtilsClone() {
        PrototypePerson person = new PrototypePerson()
                .setId("10001")
                .setAge(18)
                .setName("First sight")
                .setHobbies(CollUtil.newArrayList("Suckling", "sleep"));
        PrototypePerson clonePerson = new PrototypePerson();
        // With the help of BeanUtils auxiliary properties
        BeanUtils.copyProperties(person, clonePerson);

        clonePerson.getHobbies().add("Beat beans");
        System.out.println("person:" + person.toString());
        System.out.println("clonePerson:" + clonePerson.toString());
    }

Shallow copy JDK object Clone() mode

public class PrototypePerson implements Cloneable, Serializable {
    private String id;
    // ....
    @Override
    public PrototypePerson clone() {
        try {
            // Shallow replication using Jdk
            return (PrototypePerson) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

Special account

  1. Not all prototype patterns need to be deeply copied. Look at the actual business scenario.
  2. The original intention of prototype pattern is to reduce the stupid operation of manually copying a large number of attributes.
  3. The standard prototype pattern requires objects to implement Cloneable and serializable interfaces.
  4. Suggested method: the object implements the Serializable interface (instead of the clonable interface), and provides the cloning tool class: PrototypeUtils. The caller uses PrototypeUtils for attribute replication, which determines whether to copy shallow or deep.

PrototypeUtils tool class code

import com.alibaba.fastjson.JSON;
import org.springframework.beans.BeanUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * @description: Prototype pattern tool class
 * @author: chujian
 * @since: 2022-02-19 16:23
 **/
@SuppressWarnings("unchecked")
public class PrototypeUtils {
    /**
     * Shallow cloning
     *
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> T clone(T obj) {
        if (null == obj) return null;
        Object newInstance = null;
        try {
            newInstance = obj.getClass().getConstructor().newInstance();
            BeanUtils.copyProperties(obj, newInstance);
            return (T) newInstance;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Deep cloning
     *
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> T deepClone(T obj) {
        if (null == obj) return null;
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);

            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);

            Object o = ois.readObject();
            return (T) o;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Deep cloning through Json serialization
     *
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> T deepCloneByJson(T obj) {
        String jsonString = JSON.toJSONString(obj);
        Object object = JSON.parseObject(jsonString, obj.getClass());
        return (T) object;
    }
}

Complete source code address of this article

Code cloud code address

Keywords: Java Design Pattern

Added by dragin33 on Sat, 19 Feb 2022 13:15:26 +0200