前提
原型模式其实大家早就知道了,只是不知道这个专业术语而已,为什么这么说呢?西游记大家都看过吧,里面的孙悟空把几根毫毛一吹,就出来几只和孙悟空一模一样的猴子,这就是典型的原型模式。
介绍
上图为原型模式的UML图,有些资料把新克隆出来的对象画成是原对象的子类,我个人觉得不对,其实他们是同一层级的关系,因此在这图里我个人做了些改动。
原型模式很好理解,注意有一个Clone方法就行,恰好在JAVA语言中,已经融合了原型模式,自带了Cloneable
接口,因此我们在需要使用原型模式的时候实现Cloneable
接口,并完成克隆操作即可。
使用方式
使用原型模式有两点需要注意:
- 区分深拷贝和浅拷贝的不同
- 认识到拷贝过程不会触发构造函数
浅拷贝模式
public class Prototype implements Cloneable{
private String mName;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Prototype a = new Prototype();
a.setName("a");
Prototype b = (Prototype) a.clone();
System.out.println("a.name:"+a.getName()+"-"+a);
System.out.println("b.name:"+b.getName()+"-"+b);
}
}
结果如下:
a.name:a-applet.Prototype@2a139a55
b.name:a-applet.Prototype@15db9742
我们发现虽然a和b的mName值一样,但对象的引用值却不同,这说明产生了两个对象,而且b对象和a对象的内容一模一样。
我们在上面的例子中加以改进下:
public class Prototype implements Cloneable{
private String mName;
private List<String> mList= new ArrayList<String>();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
public List<String> getList() {
return mList;
}
public void setList(List<String> list) {
this.mList = list;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Prototype a = new Prototype();
a.setName("a");
a.getList().add("abc");
Prototype b = (Prototype) a.clone();
b.setName("b");
b.getList().add("123");
System.out.println("a.name:"+a.getName()+"-"+a);
System.out.println("b.name:"+b.getName()+"-"+b);
System.out.println("a.list:"+a.getList());
System.out.println("b.list:"+b.getList());
}
}
结果如下:
a.name:a-applet.Prototype@2a139a55
b.name:b-applet.Prototype@15db9742
a.list:[abc, 123]
b.list:[abc, 123]
是不是很奇怪?怎么a对象也有“123”?其实上面的代码就是浅拷贝带来的问题,在clone()
方法中super.clone();
仅仅帮你克隆基础数据类型,类似long,int,char,即使String类,java也把它当作基础数据类型,对于数组,列表,类,系统不会克隆,仅仅把引用传给新对象,因此相当于克隆后的对象们共同分享,在特殊场合这就会造成混乱。
深拷贝模式
public class Prototype implements Cloneable{
private String mName;
private ArrayList<String> mList= new ArrayList<String>();
@Override
protected Object clone() throws CloneNotSupportedException {
Prototype other = (Prototype) super.clone();
other.mList = (ArrayList<String>) mList.clone();
return other;
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
public List<String> getList() {
return mList;
}
public void setList(ArrayList<String> list) {
this.mList = list;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Prototype a = new Prototype();
a.setName("a");
a.getList().add("abc");
Prototype b = (Prototype) a.clone();
b.setName("b");
b.getList().add("123");
System.out.println("a.name:"+a.getName()+"-"+a);
System.out.println("b.name:"+b.getName()+"-"+b);
System.out.println("a.list:"+a.getList());
System.out.println("b.list:"+b.getList());
}
}
结果如下:
a.name:a-applet.Prototype@2a139a55
b.name:b-applet.Prototype@15db9742
a.list:[abc]
b.list:[abc, 123]
这下就得到我们想要的结果了,既然系统不帮我们做,我们只好在clone()
方法中自己再多做一些事情了,^_^
构造函数不触发
我们再修改下代码,如下:
public class Prototype implements Cloneable{
private String mName;
private ArrayList<String> mList= new ArrayList<String>();
public Prototype()
{
System.out.println("Prototype()");
}
@Override
protected Object clone() throws CloneNotSupportedException {
Prototype other = (Prototype) super.clone();
other.mList = (ArrayList<String>) mList.clone();
return other;
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
public List<String> getList() {
return mList;
}
public void setList(ArrayList<String> list) {
this.mList = list;
}
}
public class Main {
public static void main(String[] args) throws Exception {
Prototype a = new Prototype();
a.setName("a");
a.getList().add("abc");
Prototype b = (Prototype) a.clone();
b.setName("b");
b.getList().add("123");
System.out.println("a.name:"+a.getName()+"-"+a);
System.out.println("b.name:"+b.getName()+"-"+b);
System.out.println("a.list:"+a.getList());
System.out.println("b.list:"+b.getList());
}
}
结果如下:
Prototype()
a.name:a-applet.Prototype@2a139a55
b.name:b-applet.Prototype@15db9742
a.list:[abc]
b.list:[abc, 123]
发现Prototype()
只打印了一次,说明在java的原型模式机制中,构造函数不会触发,仅仅是从二进制层面拷贝了一个对象而已,因此在实际运用中,我们应该心里有数,以便遇到bug时知道哪些地方有可能会出问题。