是什么
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
该模式的核⼼思想是基于现有的对象创建新的对象,⽽不是从头开始创建。
结构
- 抽象原型接口:声明一个克隆自身的方法clone()
- 具体原型类:实现抽象原型接口的clone()方法,复制当前对象并返回一个新对象
- 访问类:使用具体原型类中的clone()方法来复制出新的对象
实现
类图:
Java中的Object类中提供了clone()
方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:
具体原型类(Realizetype):
public class Realizetype implements Cloneable {
public Realizetype() {
System.out.println("具体原型对象创建成功!");
}
@Override
protected Realizetype clone() throws CloneNotSupportedException {
System.out.println("具体原型复制成功!");
return (Realizetype) super.clone();
}
}
访问测试类:
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建一个原型类对象
Realizetype realizetype = new Realizetype();
// 调用原型类中的clone()方法进行对象克隆
Realizetype cloneRealizetype = realizetype.clone();
System.out.println("原型类对象的地址:" + realizetype);
System.out.println("克隆出来的对象的地址:" + cloneRealizetype);
System.out.println("原型类对象和克隆出来的对象是否相同:" + (realizetype == cloneRealizetype));
}
}
结果:
具体原型对象创建成功!
具体原型复制成功!
原型类对象的地址:com.jmd.prototype.demo.Realizetype@1b6d3586
克隆出来的对象的地址:com.jmd.prototype.demo.Realizetype@4554617c
原型类对象和克隆出来的对象是否相同:false
浅克隆&深克隆
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址;
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
案例
使用原型模式生成“三好学生”奖状
同一学校的“三好学生”奖状除了获奖人不同以外,其他都相同。可以使用原型模式复制多个奖状,再修改姓名即可。
类图:
具体原型类:
public class Citation implements Cloneable {
// 三好学生姓名
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
protected Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
public void show() {
System.out.println(name + "同学:在2024学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
}
访问类(测试):
public class CitationTest {
public static void main(String[] args) throws CloneNotSupportedException {
// 1 创建原型对象
Citation citation = new Citation();
// 2 克隆奖状对象
Citation citation1 = citation.clone();
citation.setName("张三");
citation1.setName("李四");
// 3 调用show()方法
citation.show();
citation1.show();
}
}
结果:
张三同学:在2024学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2024学年第一学期中表现优秀,被评为三好学生。特发此状!
总结:
相比于直接实例化对象,通过原型模式复制当前对象可以减少资源消耗,提高性能,尤其在对象的创建过程复杂或对象的创建代价较大的情况下。当需要频繁创建相似对象,并且可以通过克隆避免重复初始化工作的场景时,可以考虑使用原型模式,在克隆对象时还可以动态的添加或删除原型对象的属性,创造出相似但不完全相同的对象,提高了灵活性。
但是使用原型模式也需要考虑:如果对象内部状态包含了引用类型的成员变量,那么实现深拷贝就会变得较为复杂,需要考虑引用类型对象的克隆问题。
使用场景
- 对象的创建非常复杂
- 性能和安全要求较高
扩展(实现深克隆)
将上面案例中Citation类中的name属性修改为Student类型的的属性。
学生类:
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
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 class Citation implements Cloneable {
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@Override
protected Citation clone() throws CloneNotSupportedException {
return (Citation) super.clone();
}
public void show() {
System.out.println(student.getName() + "同学:在2024学年第一学期中表现优秀,被评为三好学生。特发此状!");
}
}
测试类:
public class CitationTest2 {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建原型对象
Citation citation = new Citation();
Student student = new Student("张三", 18);
citation.setStudent(student);
// 复制奖状
Citation citation1 = citation.clone();
// 获取citation1奖状所属学生对象
Student student1 = citation1.getStudent();
student1.setName("李四");
// 判断student和student1是否为同一个对象
System.out.println("student和student1是否为同一个对象: " + (student == student1));
// 调用show()方法
citation.show();
citation1.show();
}
}
结果:
student和student1是否为同一个对象: true
李四同学:在2024学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2024学年第一学期中表现优秀,被评为三好学生。特发此状!
student和student1对象是同一个对象,就会产生将student1对象的name属性改为“李四”,两个Citation(奖状)对象中显示的都是李四。这就是浅克隆的效果,对具体原型类(Citation)中的引用类型的属性进行引用的复制。针对此情况应使用深克隆,进行深克隆需要使用到对象流。代码如下:
public class CitationTest3 {
public static void main(String[] args) throws Exception {
// 创建原型对象
Citation citation = new Citation();
Student student = new Student("张三", 18);
citation.setStudent(student);
// 创建对象输出流对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\javaTest\\citation.txt"));
// 将citation对象写入到文件中
oos.writeObject(citation);
oos.close();
// 创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\javaTest\\citation.txt"));
// 从文件中读取奖状对象
Citation citation1 = (Citation) ois.readObject();
ois.close();
// 获取citation1奖状所属学生对象
Student student1 = citation1.getStudent();
student1.setName("李四");
// 判断student和student1是否为同一个对象
System.out.println("student和student1是否为同一个对象: " + (student == student1));
// 调用show()方法
citation.show();
citation1.show();
}
}
结果:
student和student1是否为同一个对象: false
张三同学:在2024学年第一学期中表现优秀,被评为三好学生。特发此状!
李四同学:在2024学年第一学期中表现优秀,被评为三好学生。特发此状!
注意:
Citation和Student必须实现Serializable接口,否则会抛NotSerializableException异常。
github笔记