1, Application scenario of prototype mode
You must have met such a code scenario with a large number of getter and setter assignment scenarios. For example, code snippets like this:
private SafetyStockMessage createKafkaMessage(SafetyStock safetyStock, HttpServletRequest request) { SafetyStockMessage safetyStockMessage = new SafetyStockMessage(); safetyStockMessage.setId(safetyStock.getId()); safetyStockMessage.setProvinceCode(safetyStock.getProvinceCode()); safetyStockMessage.setRequestId(CodeConstants.REQUEST_ID); safetyStockMessage.setRequestIp(CodeConstants.REQUEST_IP); safetyStockMessage.setSerial(IdMakerUtil.make32Id()); safetyStockMessage.setStockMax(safetyStock.getStockMax()); safetyStockMessage.setStockMin(safetyStock.getStockMin()); safetyStockMessage.setProvince(safetyStock.getProvince()); safetyStockMessage.setCategoryName(safetyStock.getCategoryName()); safetyStockMessage.setUpdateTime(new Date()); safetyStockMessage.setUpdateBy(getLoginUser(request)); return safetyStockMessage; }
The code looks very neat and standardized. Do you think such code is elegant? Such code belongs to pure manual labor. If we use the prototype pattern, we can solve this problem.
Prototype Pattern refers to a prototype instance that specifies the type of object to be created and creates new objects by copying such a prototype.
The prototype mode is mainly applicable to the following scenarios:
1. Class initialization consumes more resources;
2. An object generated by new needs a very tedious process (data preparation, access rights, etc.);
3. The constructors are complex;
4. A large number of objects are produced in the body of the loop.
Class structure diagram of prototype model:
2, Simple clone
A standard Prototype pattern code should be designed in this way. First create the Prototype interface:
public interface Prototype { Prototype clone(); }
Create the concrete prototype to be cloned:
public class ConcretePrototype implements Prototype { private String name; private int age; private List<String> hobbies; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public List<String> getHobbies() { return hobbies; } public void setHobbies(List<String> hobbies) { this.hobbies = hobbies; } @Override public ConcretePrototype clone() { ConcretePrototype concretePrototype = new ConcretePrototype(); concretePrototype.setName(this.name); concretePrototype.setAge(this.age); concretePrototype.setHobbies(this.hobbies); return concretePrototype; } }
To create a Client object:
public class Client { private Prototype prototype; public Client(Prototype prototype) { this.prototype = prototype; } public Prototype startClone(Prototype concretePrototype) { return (Prototype)concretePrototype.clone(); } }
Test code:
public static void main(String[] args) { //Create a specific object to be cloned ConcretePrototype concretePrototype = new ConcretePrototype(); //Fill properties, preparing for testing concretePrototype.setName("Kevin"); concretePrototype.setAge(18); List<String> hobbies = new ArrayList<>(); concretePrototype.setHobbies(hobbies); System.out.println(concretePrototype); //Create Client object, ready to clone Client client = new Client(concretePrototype); ConcretePrototype concretePrototypeClone = (ConcretePrototype) client.startClone(concretePrototype); System.out.println(concretePrototypeClone); System.out.println("The reference type address value in the clone object is:" + concretePrototypeClone.getHobbies()); System.out.println("The reference type address value in the original object is:" + concretePrototype.getHobbies()); System.out.println("Object address comparison:" + (concretePrototypeClone.getHobbies() == concretePrototype.getHobbies())); }
Operation result:
From the test results, it can be seen that the reference address of hobbies is the same, which means that the value is not copied, but the reference address is copied. If we modify the attribute values of any object, the values of the hobbies of the concretePrototype and concretePrototypeClone will change. This is what we often call shallow cloning. Only the value type data is copied completely, and the reference object is not copied. In other words, all reference objects still point to the original object, which is obviously not the result we want.
Let's continue to transform the code and use deep cloning.
3, Deep clone
Let's change the scene. We all know that monkey king, the great sage of heaven. First of all, it is a monkey with seventy-two changes. Put a hair in its mouth and blow it to make ten million monkeys. It also has a golden cudgel in its hand. The golden cudgel can be changed from big to small. This is the classic embodiment of the prototype pattern that we are familiar with.
Create the prototype Monkey class:
public class Monkey { public int height; public int weight; public Date birthday; }
Create reference object golden cudgel class:
public class GoldenCudgel implements Serializable { public float h = 100f; public float d = 10f; public void changeBig() { this.d *= 2; this.h *= 2; } public void changeSmall() { this.d /= 2; this.h /= 2; } }
Create the specific object Monkey King class of Monkey King:
public class MonkeyKing extends Monkey implements Cloneable, Serializable { public GoldenCudgel goldenCudgel; public MonkeyKing() { this.birthday = new Date(); this.goldenCudgel = new GoldenCudgel(); } @Override protected Object clone() { return this.deepClone(); } /** * Deep clone * @return */ protected Object deepClone() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); MonkeyKing copy = (MonkeyKing) ois.readObject(); copy.birthday = new Date(); return copy; } catch (IOException e) { e.printStackTrace(); return null; } catch (ClassNotFoundException e) { e.printStackTrace(); return null; } } /** * Shallow clone */ public MonkeyKing shallowClone(MonkeyKing target) { MonkeyKing monkeyKing = new MonkeyKing(); monkeyKing.height = target.height; monkeyKing.weight = target.weight; monkeyKing.goldenCudgel = target.goldenCudgel; monkeyKing.birthday = new Date(); return monkeyKing; } }
Test code:
public static void main(String[] args) throws Exception { MonkeyKing monkeyKing = new MonkeyKing(); MonkeyKing clone = (MonkeyKing)monkeyKing.clone(); System.out.println("Deep cloned:" + (monkeyKing.goldenCudgel == clone.goldenCudgel)); MonkeyKing shallow = new MonkeyKing(); MonkeyKing newMonkeyKing = shallow.shallowClone(shallow); System.out.println("Shallow clone" + (shallow.goldenCudgel = newMonkeyKing.goldenCudgel)); }
Operation result:
- Clone destroy singleton mode
If we clone the object created by singleton mode, it means that deep cloning will destroy singleton mode. How to prevent the clone from destroying a single case, and forbid the deep clone. We don't implement the clonable interface in the singleton class, just return the singleton object in overriding the clone() method. The code is as follows:
@Override protected Object clone() throws CloneNotSupportedException { return INSTANCE; }
- Clonable source code analysis
First, we can see that our common ArrayList implements the clonable interface, and then we can see the implementation of the code clone() method:
public Object clone() { try { ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }