“This is the 15th day of my Participation in the August More Text Challenge. Check out the details:August is more challenging”

1. Definition of prototype pattern

A Prototype Pattern is a Pattern in which Prototype instances specify the types of objects to be created, and new objects are created by copying these prototypes.

The core of the archetype pattern is to copy archetype objects. Using an existing object in the system as a prototype, copying directly from the in-memory binary stream eliminates the time-consuming object initialization process (no constructor calls), which greatly improves performance. When the construction of an object is time-consuming, the existing object in the current system can be used as a prototype and cloned (usually based on binary stream), avoiding the initialization process and greatly reducing the creation time of new objects.

The archetypal pattern consists of three characters:

  1. Client: The client class requests the creation of an object.
  2. Prototype: Specifies the copy interface.
  3. Concrete Prototype: The object being copied.

Note: A pattern in which objects are created by copying objects instead of using the new keyword is called a prototype pattern.

Ii. Application scenarios of prototype mode

You are bound to encounter situations where getters and setters are given too much space. (Mostly manual labor)

1. The prototype mode applies to the following scenarios

  • Class initialization consumes a lot of resources.
  • An object generated by new requires a very verbose process (data preparation, access permissions, etc.)
  • Constructors are more complex.
  • When a large number of objects are produced in the body of a loop.

In Spring, the archetypal pattern is widely used. Scope =”prototype”, for example, is also a prototype pattern in the json.parseObject () we often use.

2. Generic writing of prototype patterns

A standard prototype pattern code should be designed like this. Create IPrototype interface:

public interface IPrototype<T> {
    T clone(a);
}
Copy the code

ConcretePrototype create ConcretePrototype to clone:

@Data
public class ConcretePrototype implements Cloneable {
    private String name;
    private int age;

    @Override
    public ConcretePrototype clone(a) {
        ConcretePrototype concretePrototype = new ConcretePrototype();
        concretePrototype.setName(this.name);
        concretePrototype.setAge(this.age);
        returnconcretePrototype; }}Copy the code

Test code:

public class PrototypeTest {

    @Test
    public void test(a) {
        // Create the prototype object
        ConcretePrototype concretePrototype = new ConcretePrototype();
        concretePrototype.setName("Mark");
        concretePrototype.setAge(18);
        System.out.println(concretePrototype);

        // Copy the prototype objectConcretePrototype cloneType = concretePrototype.clone(); System.out.println(cloneType); }}Copy the code

Running result:

At this point, some people ask, is archetypal mode that simple? Yes, it’s as simple as that. In this simple scenario, it looks like the operation is getting complicated. But if there are hundreds of properties that need to be copied, then we can do it once and for all. However, the above copying process is done by ourselves. In actual coding, we usually do not waste such manual labor. The JDK already implements an existing API for us, so we only need to implement the Cloneable interface. To modify the code, modify the ConcretePrototype class:

@Data
public class ConcretePrototype implements Cloneable {
    private String name;
    private int age;

    @Override
    public ConcretePrototype clone(a) {
        try {
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null; }}}Copy the code

Run it again, and you get the same result. With the SUPPORT of the JDK, we can easily copy any number of attributes. In this test, we add a new property called “Hobbies” to the ConcretePrototype class: hobbies

@Data
public class ConcretePrototype implements Cloneable {
    private String name;
    private int age;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone(a) {
        try {
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null; }}}Copy the code

Modify the client test code:

public class PrototypeTest {

    @Test
    public void test(a) {
        // Create the prototype object
        ConcretePrototype concretePrototype = new ConcretePrototype();
        concretePrototype.setName("Mark");
        concretePrototype.setAge(18);
        List<String> hobbies = new ArrayList<>();
        hobbies.add("basketball");
        hobbies.add("coding");
        concretePrototype.setHobbies(hobbies);

        // Copy the prototype object
        ConcretePrototype cloneType = concretePrototype.clone();
        cloneType.getHobbies().add("swing");
        System.out.println("Prototype object:" + concretePrototype);
        System.out.println("Clone object:"+ cloneType); }}Copy the code

Running result:

When we added a new hobby to the cloned object, we found that the original object also changed, which was clearly not what we expected. Because we want the cloned object to be separate from the prototype object and not be related to it. From the test results, it appears that hobbies shared a memory address, which means that the referenced address was copied instead of the value. In this case, if we change the property value of either object, the prototype and cloneType bobbies values will change. This is what we call a shallow clone. Only the value type data is copied in full, no reference objects are copied. In other words, all reference objects still point to the original object, which is obviously not what we want. So how do you solve this problem? Now let’s look at deep cloning and continue the transformation.

3. Deep clone using serialization

Look at the code and add a deepClone() method:

@Data
public class ConcretePrototype implements Cloneable.Serializable {
    private String name;
    private int age;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone(a) {
        try {
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null; }}public ConcretePrototype deepClone(a) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

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

            return (ConcretePrototype) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null; }}}Copy the code

Look at the client-side call code:

@Data
public class ConcretePrototype implements Cloneable.Serializable {
    private String name;
    private int age;
    private List<String> hobbies;

    @Override
    public ConcretePrototype clone(a) {
        try {
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null; }}public ConcretePrototype deepClone(a) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

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

            return (ConcretePrototype) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null; }}}Copy the code

The running results are as follows:

Three, prototype mode in the application of source code

The Cloneable interface in JDK

public interface Cloneable {}Copy the code

The interface definition is very simple, we just need to find the source code to see which interfaces implement the Cloneable interface. Look at the implementation of the ArrayList class.

We see that the method is simply iterating through the elements in the List. At this time we think again, is this form is deep cloning? In this ConcretePrototype class, we add a new deepCloneHobbies() method:

public ConcretePrototype deepCloneHobbies(a) {
    try {
        ConcretePrototype result = (ConcretePrototype) super.clone();
        result.setHobbies((List) ((ArrayList) result.getHobbies()).clone());
        return result;
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
        return null; }}Copy the code

Modify the client code:

public class PrototypeTest {

    @Test
    public void test(a) {
        // Create the prototype object
        ConcretePrototype concretePrototype = new ConcretePrototype();
        concretePrototype.setName("Mark");
        concretePrototype.setAge(18);
        List<String> hobbies = new ArrayList<>();
        hobbies.add("basketball");
        hobbies.add("coding");
        concretePrototype.setHobbies(hobbies);

        // Copy the prototype object
        ConcretePrototype cloneType = concretePrototype.deepCloneHobbies();
        cloneType.getHobbies().add("swing");
        System.out.println("Prototype object:" + concretePrototype);
        System.out.println("Clone object:"+ cloneType); System.out.println(concretePrototype == cloneType); }}Copy the code

The running results are as follows:

Running it will give you the desired result. But code like this is hardcoded, and if you declare various collection types in an object, each case needs to be handled separately. As a result, deep cloning is usually written directly using serialization.

Four, prototype model advantages and disadvantages

Advantages:

  1. Good performance. Java’s own prototype pattern is based on a copy of an in-memory binary stream, which is much better than simply new an object.
  2. You can use deep clone to save the state of an object, and use prototype mode to copy an object and save its state, simplifying the process of creating an object, so that it can be used when needed (for example, to restore to a historical state), which can assist the implementation of the undo operation.

Disadvantages:

  1. You need to configure a clone method for each class.
  2. The clone method is located inside the class, and when an existing class is modified, the code needs to be modified, which violates the open closed principle.
  3. Deep cloning requires complex code to implement, and when there are multiple nested references between objects, in order to implement deep cloning, the corresponding classes of each layer of objects must support deep cloning, which can be difficult to implement. Therefore, deep copy and shallow copy need to be used properly.

Five, links

Design Patterns – Factory Patterns learning journey

Design Patterns – a journey through singleton patterns

Welcome to follow the wechat official account (MarkZoe) to learn from each other and communicate with each other.