说明
原型模式就是通过对原型实例的克隆,制造出一个一模一样的新对象。
实现方式就是实现Cloneable接口,然后重写Object的clone方法。
UML
代码
以Computer为例,三个不同类型的属性。
/**
* @author ctl
* @date 2021/1/14
*/
public class Computer implements Cloneable {
int memory;
String sys;
Cpu cpu;
public Computer(int memory, String sys, Cpu cpu) {
this.memory = memory;
this.sys = sys;
this.cpu = cpu;
}
@Override
protected Computer clone() throws CloneNotSupportedException {
return (Computer) super.clone();
}
public static class Cpu {
String type;
public Cpu(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public String getSys() {
return sys;
}
public void setSys(String sys) {
this.sys = sys;
}
public Cpu getCpu() {
return cpu;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
}
测试类
/**
* @author ctl
* @date 2021/1/14
*/
public class PrototypeMain {
public static void main(String[] args) throws CloneNotSupportedException {
Computer computer = new Computer(16, "mac", new Computer.Cpu("m1"));
Computer computer2 = computer.clone();
System.out.println(computer);
System.out.println(computer2);
System.out.println(computer.getSys());
System.out.println(computer2.getSys());
System.out.println(computer.getCpu());
System.out.println(computer2.getCpu());
}
}
结果
Computer对象有三个属性,类型分别是int,String和一个Cpu对象,我们可以看出,Computer对象的地址不是同一个地址,但是Cpu对象的地址是同一个地址,这是为什么呢?
这就涉及到一个深浅拷贝的问题。
浅拷贝:说白了就是两个对象指向一个地址,这时候修改一个对象,另一个可能会受到影响。
深拷贝:就是完完全全互不影响的两个对象,包括其中的属性。
以Computer对象中的三个属性来说:
基本类型不用多说,对于深浅拷贝没有影响。
String类型有些小争议,因为按String对象地址来比较的话,clone出来后的两个对象是一个地址,可以认为是浅拷贝,但因为String存储时的特殊性,修改一个对象也不会对另一个对象造成影响,所以又在结果上可以认为是深拷贝。这里不再赘述,可以专门去了解一下Java对于字符串具体是如何存储的,可以结合着jvm内存模型来一并了解,比如常量池有几种,存在哪里,不同jdk版本有什么区别,等等。
引用类型从结果上来看,自然是浅拷贝,那如何来实现深拷贝呢?
改造后的代码如下:
/**
* @author ctl
* @date 2021/1/14
*/
public class Computer implements Cloneable {
int memory;
String sys;
Cpu cpu;
public Computer(int memory, String sys, Cpu cpu) {
this.memory = memory;
this.sys = sys;
this.cpu = cpu;
}
@Override
protected Computer clone() throws CloneNotSupportedException {
Computer clone = (Computer) super.clone();
clone.setCpu(cpu.clone());
return clone;
}
public static class Cpu implements Cloneable {
String type;
@Override
protected Cpu clone() throws CloneNotSupportedException {
return (Cpu) super.clone();
}
public Cpu(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public String getSys() {
return sys;
}
public void setSys(String sys) {
this.sys = sys;
}
public Cpu getCpu() {
return cpu;
}
public void setCpu(Cpu cpu) {
this.cpu = cpu;
}
}
同样的测试类,结果如下:
可以看出,这次Cpu对象的地址也不同了,完成了这个对象的深拷贝。
其实就是把让Cpu这个对象也实现Cloneable接口并重写clone方法,然后在Computer对象的clone方法中调用Cpu的Clone方法再赋值。
总结
原型模式实现起来非常的简单,但在原理上涉及到了深浅拷贝的问题,如果对象中引用对象的层级较深,可能会出一些坑。如果所有属性对象都是自己写的还好,如果是组合了第三方框架中的对象,该对象又没有实现Cloneable接口,实现起来就更费时费力了。
可以看看这篇博客,对原型模式和深浅拷贝讲的更细。
https://blog.csdn.net/zhangjg_blog/article/details/18369201