Preface
Android Design Patterns series articles, welcome attention, ongoing updates:
1. definition
Specify the types of objects created with prototype instances, and create new objects by copying these prototypes.
2. introduction
- The prototype pattern belongs to the creation pattern.
- An existing object (prototype) creates a new object with the same internal properties as the prototype by replicating the prototype. This is the prototype pattern.
- The core of the prototype pattern is the clone method, which realizes the copy of objects.
3.UML class diagram
3.1 Role Description:
- Prototype: An abstract class or interface that declares clone methods.
- ConcretePrototype: The object to be copied.
- Client (Client Class): Where the prototype pattern is to be used.
4. implementation
4.1 Prototype (abstract prototype class):
- Normally, Prototype does not need to be defined. Because copy is a common operation, Cloneable interface is provided in Java to support copy operation, which is Prototype in prototype mode.
- Of course, prototype mode does not necessarily have to implement Cloneable interface, but there are other ways to implement it.
4.2 Create concrete prototype class and implement Cloneable interface:
//Specific prototype class, card class
public class Card implements Cloneable {//Implementing the Cloneable interface, Cloneable is just the identification interface
private int num;//Card number
private Spec spec = new Spec();//Card size
public Card() {
System.out.println("Card Execution constructor");
}
public void setNum(int num) {
this.num = num;
}
public void setSpec(int length, int width) {
spec.setLength(length);
spec.setWidth(width);
}
@Override
public String toString() {
return "Card{" +
"num=" + num +
", spec=" + spec +
'}';
}
@Override
protected Card clone() throws CloneNotSupportedException {//Rewrite clone() method, clone() method is not in Cloneable interface, but in Object
System.out.println("clone Constructors are not executed from time to time");
return (Card) super.clone();
}
}
//Specification class, with length and width attributes
public class Spec {
private int width;
private int length;
public void setLength(int length) {
this.length = length;
}
public void setWidth(int width) {
this.width = width;
}
@Override
public String toString() {
return "Spec{" +
"width=" + width +
", length=" + length +
'}';
}
}
4.3 Create client classes where you want to use the prototype pattern:
public class Client {
public void test() throws CloneNotSupportedException {
Card card1 = new Card();
card1.setNum(9527);
card1.setSpec(10, 20);
System.out.println(card1.toString());
System.out.println("----------------------");
Card card2 = card1.clone();
System.out.println(card2.toString());
System.out.println("----------------------");
}
}
Output results:
Card Execution constructor
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
clone Constructors are not executed from time to time
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
Explain:
- clone objects do not execute constructors
- clone method is not in Cloneable interface, but in Object. Cloneable is an identification interface, which can be copied on the surface. If the Cloneable interface is not implemented but the clone method is called, the error will be reported.
However, if the following code is executed:
Card card1 = new Card();
card1.setNum(9527);
card1.setSpec(10, 20);
System.out.println(card1.toString());
System.out.println("----------------------");
Card card2 = card1.clone();
System.out.println(card2.toString());
System.out.println("----------------------");
card2.setNum(7259);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");
card2.setSpec(30, 40);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");
The output results are as follows:
Card Execution constructor
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
clone Constructors are not executed from time to time
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=20, length=10}}
Card{num=7259, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=40, length=30}}
Card{num=7259, spec=Spec{width=40, length=30}}
----------------------
We will find that when we modify the reference type of the copy object (i.e. Spec field), the value of the original object will also change; but when we modify the basic object (num field), the value of the original object will not change. This involves deep and shallow copies.
5. Deep copy and shallow copy
5.1 Shallow Copy
The above example is actually a shallow copy, as shown in the following figure:
Because num is the basic data type, it's OK to copy the integer value directly. But spec is of type Spec. It is only a reference to a real Spec object. There are two ways to copy it:
1. Copy the reference value of spec in the source object directly to the spec field of the new object, which is called shallow copy.
5.2 Deep Copies
Another way to copy is to create a new identical object according to the spec pointed to in the original Card object, and assign the reference of the new object to the spec field of the newly copied Card object. This method of copy is called deep copy, as shown in the following figure:
5.3 Prototype Modification
Then, we modify the above prototype pattern to achieve deep copy, which requires that the Clone method of Card also clone the Spec object referenced by the source object.
public static class Card implements Cloneable {
private int num;
private Spec spec = new Spec();
public Card() {
System.out.println("Card Execution constructor");
}
public void setNum(int num) {
this.num = num;
}
public void setSpec(int length, int width) {
spec.setLength(length);
spec.setWidth(width);
}
@Override
public String toString() {
return "Card{" +
"num=" + num +
", spec=" + spec +
'}';
}
@Override
protected Card clone() throws CloneNotSupportedException {
System.out.println("clone Constructors are not executed from time to time");
Card card = (Card) super.clone();
card.spec = (Spec) spec.clone();//clone is also called on spec objects to achieve deep copy
return card;
}
}
public static class Spec implements Cloneable {//Spec also implements Cloneable interface
private int width;
private int length;
public void setLength(int length) {
this.length = length;
}
public void setWidth(int width) {
this.width = width;
}
@Override
public String toString() {
return "Spec{" +
"width=" + width +
", length=" + length +
'}';
}
@Override
protected Spec clone() throws CloneNotSupportedException {//clone Method for Rewriting Spec
return (Spec) super.clone();
}
}
Test code:
Card card1 = new Card();
card1.setNum(9527);
card1.setSpec(10, 20);
System.out.println(card1.toString());
System.out.println("----------------------");
Card card2 = card1.clone();
System.out.println(card2.toString());
System.out.println("----------------------");
card2.setNum(7259);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");
card2.setSpec(30, 40);
System.out.println(card1.toString());
System.out.println(card2.toString());
System.out.println("----------------------");
The output results are as follows:
Card Execution constructor
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
clone Constructors are not executed from time to time
Card{num=9527, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=20, length=10}}
Card{num=7259, spec=Spec{width=20, length=10}}
----------------------
Card{num=9527, spec=Spec{width=20, length=10}}
Card{num=7259, spec=Spec{width=40, length=30}}
----------------------
Thus, spec references in card1 and card2 point to different Spec objects, that is to say, while clone Card objects are copied, the spec objects it refers to are copied in depth.
6. Application scenarios
- If it takes a lot of resources to initialize a class, such as data, hardware, etc., prototype copies can be used to avoid these consumption.
- If you need tedious data preparation or access rights to create a new object through new, you can also use the prototype pattern.
- When an object needs to be accessed by other objects and each caller may need to modify its value, multiple objects can be copied for use by the caller, that is, protective copies.
7. advantages
- It can solve the problem of excessive consumption when creating complex objects and improve the efficiency of creating objects in some scenarios.
- Protective copy can prevent the external caller from modifying the object and ensure that the object is read-only.
8. disadvantages
- Constructors are not executed when copying objects.
- Sometimes deep and shallow copies need to be considered.
9. Source code analysis in Android
Intent in Android implements the Cloneable interface, but the clone() method creates objects through new.
public class Intent implements Parcelable, Cloneable {
//Other code outlines
@Override
public Object clone() {
return new Intent(this);//Instead of calling super.clone() to implement the copy, it is created directly through new.
}
public Intent(Intent o) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
this.mFlags = o.mFlags;
this.mContentUserHint = o.mContentUserHint;
if (o.mCategories != null) {
this.mCategories = new ArraySet<String>(o.mCategories);
}
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
if (o.mSourceBounds != null) {
this.mSourceBounds = new Rect(o.mSourceBounds);
}
if (o.mSelector != null) {
this.mSelector = new Intent(o.mSelector);
}
if (o.mClipData != null) {
this.mClipData = new ClipData(o.mClipData);
}
}
}
Conclusion:
- In fact, it is not necessarily faster to call clone() to construct an object than new. Whether clone() or new is used to create an object depends on the cost of constructing the object. If the cost of constructing an object is high or the construction is troublesome, the efficiency of using clone() is higher, otherwise new is used.