原型模式主要在于对象的克隆,所以也叫克隆模式
其实就是利用java中的Object对象中的clone方法实现一个对象的克隆。此方法需要注意的是,一个对象想要实现克隆,就必须实现一个标志性接口Cloneable
现在先来说一下浅克隆
这玩意也叫表皮克隆,简单点就是说里面的引用的引用对象没有进行实质性的克隆,而是指向一片共同空间
话不多说,直接上代码
ConcretePrototype.java
package pxx;
import java.util.List;
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
private List<String> hobbies;
@Override
public ConcretePrototype clone() {
try {
return (ConcretePrototype)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
", hobbies=" + hobbies +
'}';
}
}
下面去看客户端Client.java
package pxx;
import java.util.ArrayList;
import java.util.List;
public class Client {
public static void main(String[] args) {
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Tom");
List<String> hobbies = new ArrayList<String>();
hobbies.add("书法");
hobbies.add("美术");
prototype.setHobbies(hobbies);
//拷贝原型对象
ConcretePrototype cloneType = prototype.clone();
cloneType.setAge(520);
cloneType.getHobbies().add("技术控");
System.out.println("原型对象:" + prototype);
System.out.println("克隆对象:" + cloneType);
System.out.println(prototype == cloneType);//数据一样,地址一样
System.out.println("原型对象的爱好:" + prototype.getHobbies());
System.out.println("克隆对象的爱好:" + cloneType.getHobbies());
System.out.println(prototype.getHobbies() == cloneType.getHobbies());//地址一样
}
}
直接上一下打印结果
下面在来上一下浅拷贝的具体分析
上面就很明显看到复制过去一个对象之后,就相当于在堆上面开了一个新空间,你做出相应的改值之后,这个只是对于普通类型来说,是对原对象互不影响的
所以上面prototype == cloneType这个进行判断的时候,会出现false,因为这两者的内存地址本来就不一样。这也就是为什么prototype.getHobbies() == cloneType.getHobbies() 的时候为什么返回true的原因,因为他们指向的内存地址都一样。
下面我们进入深克隆,解决prototype.getHobbies() == cloneType.getHobbies()内存地址不一样的问题
话不多说,直接上代码
ConcretePrototype.java
package pxx;
import java.util.ArrayList;
import java.util.List;
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
private List<String> hobbies;
@Override
public ConcretePrototype clone() {
try {
//无非就是把hobbies也进行一个克隆
//ArrayList集合对象本身就是实现了Cloneable接口的
//所以ArrayList可以直接实现克隆方法
//但是List接口是没有去实现这个Cloneable接口的
ConcretePrototype result = (ConcretePrototype)super.clone();
//把这个对象内部的集合对象也做一个克隆
result.hobbies = (List)((ArrayList)result.getHobbies()).clone();
return result;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
", hobbies=" + hobbies +
'}';
}
}
客户端代码还是不改变,看一下运行结果
直接一张图分析一下它的克隆原理
运行结果分析:
上面两个hobbies都指向了不同对象,自然比较内存地址是不相同的
但是你想想,现在也会存在这样一个问题,就是如果一个类里面有很多引用对象,如果要进行深拷贝,我们就要不断的去进行克隆,比如如下
ConcretePrototype.java
package pxx;
import java.util.ArrayList;
import java.util.List;
public class ConcretePrototype implements Cloneable {
private int age;
private String name;
//下面两个对象都是可变对象
private List<String> hobbies;
private BasePerson basePerson;
@Override
public ConcretePrototype clone() {
try {
//无非就是把hobbies也进行一个克隆
//ArrayList集合对象本身就是实现了Cloneable接口的
//所以ArrayList可以直接实现克隆方法
//但是List接口是没有去实现这个Cloneable接口的
ConcretePrototype result = (ConcretePrototype)super.clone();
//把这个对象内部的集合对象也做一个克隆
result.hobbies = (List)((ArrayList)result.getHobbies()).clone();
//因为现在还有一个对象
//注意BasePerson这个对象重写一下clone()方法
result.basePerson = (BasePerson)(result.getBasePerson()).clone();
return result;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public BasePerson getBasePerson() {
return basePerson;
}
public void setBasePerson(BasePerson basePerson) {
this.basePerson = basePerson;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
", hobbies=" + hobbies +
", basePerson=" + basePerson +
'}';
}
}
//想要被克隆,必须去实现Cloneable接口
class BasePerson implements Cloneable{
private int age;
private String name;
public BasePerson(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "BasePerson{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Client.java
package pxx;
import java.util.ArrayList;
import java.util.List;
public class Client {
public static void main(String[] args) {
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Tom");
List<String> hobbies = new ArrayList<String>();
BasePerson basePerson = new BasePerson(20,"周杰伦");
prototype.setBasePerson(basePerson);
hobbies.add("书法");
hobbies.add("美术");
prototype.setHobbies(hobbies);
//拷贝原型对象
ConcretePrototype cloneType = prototype.clone();
cloneType.setAge(520);
cloneType.getHobbies().add("技术控");
//这里是深拷贝,那么就是说,我们通过拷贝对象去修改BasePerson里面的值
//不会影响原对象
cloneType.getBasePerson().setAge(520);
System.out.println("原型对象:" + prototype);//这里没有技术控,因为hobbies对象是两个不同对象
System.out.println("克隆对象:" + cloneType);//这里的age与prototype也不一样,是520
System.out.println(prototype == cloneType);//false
System.out.println("原型对象的爱好:" + prototype.getHobbies());
System.out.println("克隆对象的爱好:" + cloneType.getHobbies());
System.out.println(prototype.getHobbies() == cloneType.getHobbies());//false
//这里面表示的内存地址也不一样
System.out.println(prototype.getBasePerson() == cloneType.getBasePerson());//false
}
}
运行结果:
上面如果引用对象太多,克隆操作相对来说,还是比较复杂的。下面我们可以采用序列化方法来实现深度克隆,也就是我们直接把这个对象给序列化到内存里面,然后在解析出来,这样就会整体在内存里面开一个新对象与原对象无任何关系。而且,此时这个对象内部的引用对象不需要显示实现Cloneable接口,重写clone方法,与此无关
ConcretePrototype.java
package pxx;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class ConcretePrototype implements Cloneable,Serializable{
private int age;
private String name;
//下面两个对象都是可变对象
private List<String> hobbies;
private BasePerson basePerson;
@Override
public ConcretePrototype clone() {
//后面要用到这个流里面的数据,所以,必须拿出来
//另外这个流不需要关闭
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)
){
//你必须明白你要序列化的是哪一个对象
objectOutputStream.writeObject(this);
} catch (IOException e) {
e.printStackTrace();
}
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)
){
//开始把对象写到内存里面去
ConcretePrototype concretePrototype = (ConcretePrototype) objectInputStream.readObject();
return concretePrototype;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public BasePerson getBasePerson() {
return basePerson;
}
public void setBasePerson(BasePerson basePerson) {
this.basePerson = basePerson;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public String toString() {
return "ConcretePrototype{" +
"age=" + age +
", name='" + name + '\'' +
", hobbies=" + hobbies +
", basePerson=" + basePerson +
'}';
}
}
//想要被克隆,必须去实现Cloneable接口
//序列化一个类必须实现Serializable
class BasePerson implements Serializable{
private int age;
private String name;
public BasePerson(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "BasePerson{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
/*@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}*/
}
client.java这个还是没变
package pxx;
import java.util.ArrayList;
import java.util.List;
public class Client {
public static void main(String[] args) {
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype();
prototype.setAge(18);
prototype.setName("Tom");
List<String> hobbies = new ArrayList<String>();
BasePerson basePerson = new BasePerson(20,"周杰伦");
prototype.setBasePerson(basePerson);
hobbies.add("书法");
hobbies.add("美术");
prototype.setHobbies(hobbies);
//拷贝原型对象
ConcretePrototype cloneType = prototype.clone();
cloneType.setAge(520);
cloneType.getHobbies().add("技术控");
//这里是深拷贝,那么就是说,我们通过拷贝对象去修改BasePerson里面的值
//不会影响原对象
cloneType.getBasePerson().setAge(520);
System.out.println("原型对象:" + prototype);//这里没有技术控,因为hobbies对象是两个不同对象
System.out.println("克隆对象:" + cloneType);//这里的age与prototype也不一样,是520
System.out.println(prototype == cloneType);//false
System.out.println("原型对象的爱好:" + prototype.getHobbies());
System.out.println("克隆对象的爱好:" + cloneType.getHobbies());
System.out.println(prototype.getHobbies() == cloneType.getHobbies());//false
//这里面表示的内存地址也不一样
System.out.println(prototype.getBasePerson() == cloneType.getBasePerson());//false
}
}
运行结果
简单说一下原理