Design Mode – Prototype Mode (2)


Become as

Hello everyone, I am silent. In this class, we are going to talk a little bit more about the prototype pattern. We are going to talk about deep copy and shallow copy

Antecedents feed

Last time, we talked about Xiao Li’s job search, and we wrote a case study about the prototype pattern. Did you notice that when we used the prototype pattern to create a Resume object, the properties of the class Resume were all strings, in other words, simple types. So let’s say that now we add a property, There needs to be a reference on the resume, which is the object of Witness type. Then, let’s think about this question: when the prototype copy is made again, how will the Witness attribute be handled? Should I make a copy or make a reference to an existing Witness? Realizing this is a matter of practice, so let’s test it with the case code

Case presentation

/ / resume classes
public class Resume implements Cloneable{

    private String name;

    private String position;

    private String salary;

    public Witness witness; / / references
    
    // constructor /get/set/toString... omit
}

// Prove human
public class Witness {

    private String name;

    private String job; / / position
    
    // constructor /get/set/toString... omit
}

// Test using prototype mode
public class Client {

    public static void main(String[] args) {

        Resume resume = new Resume("Xiao li".Haidian District."Negotiable");

        resume.setWitness(new Witness("Uncle"."CTO pig Farm"));

        Resume clone = (Resume)resume.clone(); // Clone two copies using prototype mode

        Resume clone1 = (Resume)resume.clone();

        Resume clone2 = (Resume)resume.clone();

        System.out.println(clone.getWitness().hashCode()); / / testSystem.out.println(clone1.getWitness().hashCode()); System.out.println(clone2.getWitness().hashCode()); }}Copy the code

As you can see, we have cloned three copies using prototype mode and found that the hashes of the three member variables witness are the same. That is to say, by default, witness is not copied, but a reference is made to the existing witness. This situation is called shallow copy

Shallow copy

Next, let’s take a look at the introduction of shallow copy

For member variables whose data type is a primitive data type, the shallow copy passes the value directly, by making a copy of the property value to the new object


For a data type is a member of the reference data type variables, such as a member variable is an array, a class of objects and so on, the shallow copy will be for reference, which is just the member variables (memory address) a copy of reference values to the new object, actually two objects of the member variables point to the same instance, this kind of circumstance, Modifying a member variable in one object affects the value of the member variable in another object

Therefore, the case of Xiao Li’s job search mentioned above is a shallow copy, which is implemented using the default Clone () method

What if, in the real world, we want to make a copy of a member variable whose data type is a reference data type? So let’s talk a little bit about deep copy and how deep copy is implemented

Deep copy

The basic introduction of deep copy is as follows

Copy the member variable values of all the base data types of the object

Allocates storage space for all reference data type member variables, and copies the object referenced by each reference data type member variable until the object is reachable to all objects. In other words, to make a deep copy of an object, the entire object (including the reference type of the object) is copied

Deep copy is actually implemented in two ways, either by overriding the Clone () method or by object serialization

Implementation method 1: Rewrite clone() method

The sample code is shown below

public class DeepCloneableTarget implements Serializable.Cloneable {

	private static final long serialVersionUID = 1L;

	private String cloneName;

	private String cloneClass;

	public DeepCloneableTarget(String cloneName, String cloneClass) {
		this.cloneName = cloneName;
		this.cloneClass = cloneClass;
	}

	// Since the class attributes are String, we use the default clone method to do just that
	@Override
	protected Object clone(a) throws CloneNotSupportedException {
		return super.clone(); }}public class DeepProtoType implements Serializable.Cloneable {

    private String name; / / type String

    private DeepCloneableTarget deepCloneableTarget; // Reference type

    public DeepProtoType(a) {
        super(a); }// Deep copy - Method 1: Override the clone method
    @Override
    protected Object clone(a) throws CloneNotSupportedException {

        Object deep = null;

        // Copy the basic data type and the String type
        deep = super.clone();

        // The attributes of the reference type are processed separately
        DeepProtoType deepProtoType = (DeepProtoType) deep;

        deepProtoType.deepCloneableTarget= (DeepCloneableTarget) deepCloneableTarget.clone();


        returndeepProtoType; }}public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        DeepProtoType deepProtoType = new DeepProtoType();

        deepProtoType.name = "Xiao li";

        deepProtoType.deepCloneableTarget = new DeepCloneableTarget("老李"."Clone class");

        // Mode one completes the deep copy
        DeepProtoType clone = (DeepProtoType) deepProtoType.clone();

        DeepProtoType clone1 = (DeepProtoType) deepProtoType.clone();

        System.out.println(clone.deepCloneableTarget.hashCode());

        System.out.println(clone1.deepCloneableTarget.hashCode()); // The hash values are different}}Copy the code

As you can see, when tested, the hash values are different, indicating that the entire object (including the reference type of the object) has indeed been copied, implementing deep copy

Clone () calls super.clone() to clone primitives and String primitives in the same way. The clone() method is used to copy the value of the property to the new object, and then the properties of the reference type are processed separately


DeepProtoType uses clone() to copy member values of multiple reference types. What if a reference type is a class that still has member attributes for the reference type?

Cascade processing demonstration

If the reference type is a class, it still has the member attributes of the reference type in it. Again, the idea is to distribute them separately. Let’s say that class DeepProtoType has the member attribute deepCloneableTarget, The DeepCloneableTarget class has member attributes witness, and the clone() method is overridden to complete the deep copy. The sample code is shown in the figure below

// Mode 1 Upgrade: Test the cascading clone
public class DeepProtoType implements Serializable.Cloneable {

    public String name; / / type String

    public DeepCloneableTarget deepCloneableTarget; // Reference type

    public DeepProtoType(a) {
        super(a); }// Deep copy - Method 1: Override the clone method for cascading

    @Override
    protected Object clone(a) throws CloneNotSupportedException {

        Object deep = null;

        // Copy the basic data type and the String type
        deep = super.clone();

        // The attributes of the reference type are processed separately
        DeepProtoType deepProtoType = (DeepProtoType) deep;

        deepProtoType.deepCloneableTarget= (DeepCloneableTarget) deepCloneableTarget.clone();

        deepProtoType.deepCloneableTarget.witness = (Witness) deepProtoType.deepCloneableTarget.witness.clone();

        return deepProtoType;
    }

    @Override
    public String toString(a) {
        return "DeepProtoType{" +
                "name='" + name + '\' ' +
                ", deepCloneableTarget=" + deepCloneableTarget +
                '} '; }}public class DeepCloneableTarget implements Serializable.Cloneable {

	private static final long serialVersionUID = 1L;

	private String cloneName;

	private String cloneClass;

	public Witness witness; // Add a reference type

	public DeepCloneableTarget(String cloneName, String cloneClass) {
		this.cloneName = cloneName;
		this.cloneClass = cloneClass;
	}

	// Since the class attributes are String, we use the default clone method to do just that
	@Override
	protected Object clone(a) throws CloneNotSupportedException {
		return super.clone();
	}

	@Override
	public String toString(a) {
		return "DeepCloneableTarget{" +
				"cloneName='" + cloneName + '\' ' +
				", cloneClass='" + cloneClass + '\' ' +
				", witness=" + witness +
				'} '; }}// Prove human
public class Witness implements Serializable.Cloneable {

    private String name;

    private String job; / / position

    public Witness(String name, String job) {
        this.name = name;
        this.job = job;
    }

    public String getName(a) {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getJob(a) {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    @Override
    public String toString(a) {
        return "Witness{" +
                "name='" + name + '\' ' +
                ", job='" + job + '\' ' +
                '} ';
    }

    @Override
    protected Object clone(a) throws CloneNotSupportedException {
        return super.clone(); }}// Mode 1 Upgrade: Test the cascading clone
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        DeepProtoType deepProtoType = new DeepProtoType();

        deepProtoType.name = "Xiao li";

        deepProtoType.deepCloneableTarget = new DeepCloneableTarget("老李"."Clone class");

        deepProtoType.deepCloneableTarget.witness = new Witness("References"."Position");

        DeepProtoType clone = (DeepProtoType) deepProtoType.clone();

        DeepProtoType clone1 = (DeepProtoType) deepProtoType.clone();

        System.out.println(clone);

        System.out.println(clone1);

        System.out.println(clone.deepCloneableTarget.witness.hashCode());

        System.out.println(clone1.deepCloneableTarget.witness.hashCode());
        // Test hash values are different to implement deep copy}}Copy the code

Implementation method two: through object serialization

Above our way demonstrates a, has realized the deep copy, but at the same time also poses a problem, if there are a lot of members of a reference type attributes, or (reference type is a class, there are still members of a reference type attribute) this cascade, need to deal with step by step, if you want to copy the object structure is complex, implement is very tedious, Is there a one-step solution? Let’s demonstrate the second, using the object serialization form, as shown in the example code

Shown below

public class DeepProtoType implements Serializable.Cloneable {

    public String name; / / type String

    public DeepCloneableTarget deepCloneableTarget; // Reference type

    public DeepProtoType(a) {
        super(a); }// Deep copy - Method 2: Serialize objects (recommended)
    public Object deepClone(a){

        // Create a stream object
        ByteArrayOutputStream bos = null;

        ObjectOutputStream oos = null;

        ByteArrayInputStream bis = null;

        ObjectInputStream ois = null;

        try {
            / / the serialization
            bos = new ByteArrayOutputStream();

            oos = new ObjectOutputStream(bos);

            oos.writeObject(this);// Serialize the current object as a stream of objects.
            // Include the reference type in the output

            // deserialize
            bis = new ByteArrayInputStream(bos.toByteArray());// Read the output object back in

            ois = new ObjectInputStream(bis);

            DeepProtoType clone = (DeepProtoType)ois.readObject();
            // Take full advantage of serialization and deserialization, output the current object this as a stream of objects, and then read back as an object. The associated reference type is also read back

            return clone;

        } catch (Exception e) {

            e.getMessage();

        } finally {

            // Close the stream operation
            try {
                bos.close();
                
                oos.close();
                
                bis.close();
                
                ois.close();
                
            } catch(IOException e) { e.getMessage(); }}return null;
    }

    @Override
    public String toString(a) {
        return "DeepProtoType{" +
                "name='" + name + '\' ' +
                ", deepCloneableTarget=" + deepCloneableTarget +
                '} '; }}public class DeepCloneableTarget implements Serializable.Cloneable {

	/ * * * * /
	private static final long serialVersionUID = 1L;

	private String cloneName;

	private String cloneClass;

	public DeepCloneableTarget(String cloneName, String cloneClass) {
		this.cloneName = cloneName;
		this.cloneClass = cloneClass;
	}

	// Since the class attributes are String, we use the default clone method to do just that
	@Override
	protected Object clone(a) throws CloneNotSupportedException {
		return super.clone();
	}

	@Override
	public String toString(a) {
		return "DeepCloneableTarget{" +
				"cloneName='" + cloneName + '\' ' +
				", cloneClass='" + cloneClass + '\' ' +
				'} '; }}// Test method two through object serialization
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        DeepProtoType deepProtoType = new DeepProtoType();

        deepProtoType.name = "Xiao li";

        deepProtoType.deepCloneableTarget = new DeepCloneableTarget("老李"."Clone class");

        DeepProtoType clone = (DeepProtoType)deepProtoType.deepClone();

        DeepProtoType clone1 = (DeepProtoType)deepProtoType.deepClone();

        System.out.println(clone.toString());

        System.out.println(clone1.toString());

        System.out.println(clone.deepCloneableTarget.hashCode());

        System.out.println(clone1.deepCloneableTarget.hashCode()); // Test for hash inconsistencies}}Copy the code

As you can see, after the test, the hash values are not the same, that is indeed the entire object (including object reference type) have been copied, we also realized the deep copy by means of object serialization, and advantage of the characteristics of serialization and deserialization, the current object this output in the form of object flow, and then read back in the form of objects, This method is recommended because it serializes and deserializes the entire object directly. No matter how complex the structure of the class is, it can be handled by object serialization

Matters needing attention

This is the end of the prototype pattern, let’s summarize the considerations of the prototype pattern, analyze the advantages and disadvantages and application scenarios

advantage

When creating new objects is complicated, the prototype pattern can be used to simplify the object creation process and improve efficiency

Instead of reinitializing the object, the state of the object at run time is dynamically obtained

If the original object changes (adding or subtracting attributes), the other clones will change accordingly, without changing the code

disadvantage

We need to provide a clone method for each class, which is not difficult for a brand new class. However, when modifying an existing class, we need to modify its source code, which will violate the OCP principle. This point needs to be mentioned to our friends

Next day forecast

OK, in the next section, we will enter the learning mode of builder mode. I hope you can feel the interesting part of design mode in the learning process, and learn efficiently and happily. See you next time