简介
原型模式(Prototype Pattern)指的是用一个已经创建的对象作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
原型模式的角色
- 抽象原型类:规定具体原型对象必须实现的 \(clone()\) 方法
- 具体原型类:实现抽象原型类的 \(clone()\) 方法,它是可被复制的对象
- 访问类:使用具体原型类的 \(clone()\) 方法来复制新的对象
原型模式的类型
- 浅克隆:创建一个新对象,新对象的属性和原来的对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,而不是指向原来对象的地址
原型模式的使用场景
- 对象的创建过程十分复杂,可以使用原型模式快速创建对象
- 性能和安全要求比较高
正文
浅克隆
世上第一只克隆羊 Dolly 人尽皆知,下面就以此写一份代码:
母羊的代码:
public class Sheep implements Cloneable {
private String gene;//非基本类型,因此浅克隆得到的是引用类型
@NonNull
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public String getGene() {
return gene;
}
public void setGene(String gene) {
this.gene = gene;
}
}
克隆出 Dolly 的代码:
private void shallowClone() {
Sheep mother = new Sheep();
mother.setGene("456");
try {
Sheep Dolly = (Sheep) mother.clone();
Log.i("Clone", "mother == Dolly? " + (mother == Dolly));
Log.i("Clone", (mother.getGene() == Dolly.getGene()) + "");
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
运行截图如下:
由上述代码不难看出,实现 \(Cloneable\) 接口的克隆方式是一种浅克隆。Dolly 和母羊明显是不同的个体,但是有相同的基因片段(视为引用类型),因此使用浅克隆是合适的。
对于引用类型的浅克隆,我们有一个共识:修改克隆的对象的引用类型属性后,被克隆对象的对应属性也一同修改(准确点说是修改的同一个属性)。不妨对 Dolly 的案例进行测试:
private void shallowClone() {
Sheep mother = new Sheep();
mother.setGene("456");
try {
Sheep Dolly = (Sheep) mother.clone();
Log.i("Clone", "mother == Dolly? " + (mother == Dolly));
Log.i("Clone", (mother.getGene() == Dolly.getGene()) + "");
Dolly.setGene("123");
Log.i("Clone", (mother.getGene() == Dolly.getGene()) + "");
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
运行截图如下:
既然对于非基本类型的浅克隆都是引用类型,为什么会出现修改 Dolly 的 String 类型的 gene 属性时,母羊没有同时修改呢?
答:String 类型虽然属于非基本类型,但是 Java 存在常量池机制,不同的 String 类型字符串都会存储在常量池中,因此不同对象的 gene 指向了不同的地址。
因此我们需要对 String 这个非基本类型特别看待,那么我们不妨用其他非基本类型的数据对“引用类型”这个性质进行验证。
深克隆
假设同学 A 和同学 B 是同班同学。每个学生都有姓名、性别等属性,这些属性都是非基本类型 String,也有年龄(int)、身高(float)等属性,这些属性都是基本类型。
对每个同学而言,都具备姓名、性别、年龄和身高等属性,且是个人特有的。假设我们想由同学 A 对象克隆出同学 B 对象,那么非基本类型再使用浅克隆就不合适了。因为两位同学的名字、性别等明显是不共享的,因此不可以使用引用类型,而是要用拷贝类型,所以要使用深克隆。
对于深克隆的参考文章为:https://www.cnblogs.com/gollong/p/9668699.html
对于引用类型,我们将其也实现 Cloneable 接口,这样子就可以使得引用类型的属性也变为拷贝类型
实现方式一:clone()函数的嵌套调用
定义一个学生类 Student.java
学生类
public class Student implements Cloneable {
private Information information;
private int schoolId;
public Student(int schoolId, int age, float height) {
this.schoolId = schoolId;
this.information = new Information(age, height);
}
public Information getInformation() {
return information;
}
public void setInformation(Information information) {
this.information = information;
}
public int getSchoolId() {
return schoolId;
}
public void setSchoolId(int schoolId) {
this.schoolId = schoolId;
}
@Override
public Student clone() throws CloneNotSupportedException {
Student student = (Student) super.clone();
student.information = (Information) information.clone();
return student;
}
}
定义一个信息类 Information.java
信息类
public class Information implements Cloneable {
private int age;
private float height;
public Information(int age, float height) {
this.age = age;
this.height = height;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "age: " + getAge() + " height: " + getHeight();
}
}
测试深克隆模式实现方式一
clone() 方法的嵌套调用测试
private void testDeepClone() {
Student stu1 = new Student(1, 15, 165.5f);
try {
Student stu2 = stu1.clone();
System.out.println("修改前:");
System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString()));
System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation());
System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation());
System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId()));
System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation()));
System.out.println("修改后:");
stu1.setSchoolId(2);
stu2.getInformation().setAge(18);
stu2.getInformation().setHeight(175.3f);
System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString()));
System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation());
System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation());
System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId()));
System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
运行截图如下:
实现方式二:序列化
clone() 方法的嵌套调用,已经可以实现深克隆的需求。但是当所要克隆的类嵌套深度较大,或者数据结构较为复杂的情况下,方式一就略显复杂,并且不符合开闭原则。而序列化只需要为每一个类实现一个 Serializable 接口,最后通过序列化和反序列化即可实现深克隆。
定义一个学生类 Student.java
学生类
public class Student implements Serializable {
private Information information;
private int schoolId;
public Student(int schoolId, int age, float height) {
this.schoolId = schoolId;
this.information = new Information(age, height);
}
public Information getInformation() {
return information;
}
public void setInformation(Information information) {
this.information = information;
}
public int getSchoolId() {
return schoolId;
}
public void setSchoolId(int schoolId) {
this.schoolId = schoolId;
}
}
定义一个信息类 Information.java
信息类
public class Information implements Serializable {
private int age;
private float height;
public Information(int age, float height) {
this.age = age;
this.height = height;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
@Override
public String toString() {
return "age: " + getAge() + " height: " + getHeight();
}
}
测试深克隆模式实现方式二
序列化测试
private void testDeepClone() {
Student stu1 = new Student(1, 15, 165.5f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos;
try {
oos = new ObjectOutputStream(baos);
oos.writeObject(stu1);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Student stu2 = (Student) ois.readObject();
System.out.println("修改前:");
System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString()));
System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation());
System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation());
System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId()));
System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation()));
System.out.println("修改后:");
stu1.setSchoolId(2);
stu2.getInformation().setAge(18);
stu2.getInformation().setHeight(175.3f);
System.out.println(stu1.toString() + " " + stu2.toString() + " 相等与否?" + stu1.toString().equals(stu2.toString()));
System.out.println("stu1的信息:" + stu1.getSchoolId() + " " + stu1.getInformation());
System.out.println("stu2的信息:" + stu2.getSchoolId() + " " + stu2.getInformation());
System.out.println("学校:" + (stu1.getSchoolId() == stu2.getSchoolId()));
System.out.println("信息:" + (stu1.getInformation() == stu2.getInformation()));
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
运行截图如下: