The Prototype pattern is a creational pattern that allows creating new objects by cloning a prototype object instead of creating a new one from scratch. The prototype object serves as a blueprint for creating new objects with the same properties and behaviors.
In the Prototype pattern, there are two types of copying: shallow copying and deep copying. Shallow copying creates a new object but shares its internal state with the original prototype object. Deep copying creates a new object and copies all of the prototype’s internal state to the new object.
The benefits of using shallow copying and deep copying in the Prototype pattern are as follows:
1. Shallow Copying
When an object is shallow copied, a new object is created with the same values for each field as the original object. If some fields of the object are references to other objects, then the new object will have references to the same objects as the original object. It means that changes to the referenced objects will affect both the original object and the new object. If the object contains mutable state that is shared among multiple objects, shallow copying helps prevent duplication of the mutable state.
For example, suppose we have a Person class that contains an array of phone numbers that they own. A shallow copy implementation will create a new Person object, but both the original and new Person objects will reference the same phone numbers array. If we update the phone numbers array in the original person object, the changes will be reflected in the new person object as well.
Here is an example of shallow copying in Java using the clone() method:
public class Person implements Cloneable {
private String name;
private String[] phoneNumbers;
public Person(String name, String[] phoneNumbers) {
this.name = name;
this.phoneNumbers = phoneNumbers;
}
public void setPhoneNumbers(String[] phoneNumbers) {
this.phoneNumbers = phoneNumbers;
}
public Object clone() {
try {
return super.clone();
} catch(CloneNotSupportedException e) {
return null;
}
}
}
//Shallow copy example
String[] phoneNumbers = {"555-1234", "555-5678"};
Person prototype = new Person("John Doe", phoneNumbers);
Person clone = (Person) prototype.clone();
System.out.println(clone.getName()); //John Doe
clone.setPhoneNumbers(new String[]{"555-4444", "555-5555"});
System.out.println(prototype.getPhoneNumbers()[0]); //555-4444In this example, when we set the phone numbers of the clone object, the phone numbers of the prototype object are updated as well.
2. Deep Copying
When an object is deep copied, a new object is created with its own copy of all the fields and the objects they reference. It means that changes to the new object’s internal state will not affect the original object’s state. Deep copying is useful if the original object contains objects with mutable state we want to change independently.
For example, consider the same Person class example as before, but now with a mutable Address object that contains street, city and zipCode fields. If we create a deep copy of the Person object, we want a new Address object to be created as well, giving each Person object its own copy of the mutable Address object.
Here is an example of deep copying in Java using serialization:
public class Address implements Serializable {
private String street;
private String city;
private String zipCode;
//getters and setters
}
public class Person implements Serializable {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
// Getters and setters
public Person deepCopy() throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream =
new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (Person) objectInputStream.readObject();
}
}
//Deep copy example
Address address = new Address("123 Main St", "Anytown", "12345");
Person prototype = new Person("John Doe", address);
Person clone = prototype.deepCopy();
address.setZipCode("67890");
System.out.println(clone.getAddress().getZipCode()); //12345
System.out.println(prototype.getAddress().getZipCode()); //67890In this example, when we set the zip code property of the address object in the original person object, it does not affect the zip code of the clone person object because we created a new address object in the deep copy process.
To sum up, the Prototype pattern provides great benefits for object creation by allowing objects to be created from a prototype object. Using shallow and deep copying can further enhance the pattern’s benefits, depending on the application’s requirements. Shallow copying is useful to prevent duplication of the mutable state of an object, whereas deep copying creates independent copies of an object’s mutable state.