一、基础概念
通过复制已有对象作为原型,通过复制该原型来返回一个新对象,而不是新建对象,说白了就是不断复制相同的对象罢了。
二、UML类图
三、角色分析
角色 | 描述 |
---|---|
抽象原型类 | 规定了具体的原型对象必须实现的clone()方法 |
具体原型类 | 实现抽象原型类的clone()方法,它是可以为复制的对象 |
访问类 | 使用具体原型类中的clone()方法来复制新的对象 |
四、案例分析
1、通用实现(浅克隆)
定义一个学生类,实现Cloneable接口并重写clone方法。
super.clone()是基于对象在内存中的二进制位面值进行复制的一种浅拷贝实现。它的优点是效率高,不需要进行逐字段复制,因此不会调用对象的构造函数,也就是不需要经历初始化的过程。
@Override
protected Student clone(){
Student student = null;
try {
student = (Student) super.clone();
}catch (Exception e){
e.printStackTrace();
}
return student;
}
其内部属性有name和Teacher类,实现有参构造,get和set方法,重写toString()方法。
public class Student implements Cloneable{
private String name;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", teacher=" + teacher +
'}';
}
private Teacher teacher;
public Student(String name, Teacher teacher) {
this.name = name;
this.teacher = teacher;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
protected Student clone(){
Student student = null;
try {
student = (Student) super.clone();
}catch (Exception e){
e.printStackTrace();
}
return student;
}
}
写一个主方法测试:
public static void main(String[] args) {
Teacher teacher = new Teacher("赵老师");
Student student = new Student("李四",teacher);
Student clone = student.clone();
clone.getTeacher().setName("老王老师");
System.out.println(student);
System.out.println(clone);
}
运行结果如下:
运行完毕以后会发现一个问题,就是我克隆出来的学生换了新的老王老师以后,怎么原来学生对象的老师也变成了老王老师,这明显不对呀!
从运行的结果上分析,应该是teacher共用同一个内存地址,意味着复制的不是值,而是引用的地址,这正是浅拷贝的特征:
1、对基本数据类型进行值复制
2、对引用类型仅复制引用,没有复制引用的对象
解决办法是在clone时,需要深拷贝teacher对象,断开student和clone的teacher对象引用关系,使两者独立。
2、深克隆
Student类需要实现Serializable接口,并自定义了deepClone方法实现深克隆,每一行代码解释如下:
1、创建字节数组输出流,用于存放序列化后的二进制数据。
ByteArrayOutputStream bos = new ByteArrayOutputStream();
2、 基于字节数组输出流创建对象输出流,用于序列化对象。
ObjectOutputStream oos = new ObjectOutputStream(bos);
3、 将当前对象写入对象输出流进行序列化,序列化后的二进制数据存入字节数组输出流。
oos.writeObject(this);
4、获取字节数组输出流中的数据(序列化后的二进制数据),封装为字节数组输入流。
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
5、基于字节数组输入流创建对象输入流,用于反序列化对象。
ObjectInputStream ois = new ObjectInputStream(bis);
6、从对象输入流中读取流数据并反序列化生成对象,返回反序列化得到的学生对象副本。
return (Student) ois.readObject();
完整的关键代码如下:
public class Student implements Cloneable, Serializable {
//构造、get和set方法、toString()方法省略
public Student deepClone(){
try{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Student) ois.readObject();
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
总结起来,这段代码使用了序列化和反序列化来实现对象的深克隆,核心思路是将对象写入流,然后从流里再读出来克隆对象。
同时Teacher类也需要实现Serializable序列化接口,关键代码如下:
public class Teacher implements Serializable {}
但不需要实现深克隆,原因如下:
在通过序列化实现Student深克隆时,会自动将Student对象所引用的Teacher对象全部序列化,并在反序列化时重新创建出一个新的Teacher对象。
Teacher对象已经在这个序列化/反序列化的过程中被自动深克隆了,不需要再单独实现深克隆方法。
主方法测试:
public static void main(String[] args) {
Teacher teacher = new Teacher("赵老师");
Student student = new Student("李四",teacher);
Student clone = student.deepClone();
clone.getTeacher().setName("老王老师");
System.out.println(student);
System.out.println(clone);
}
运行结果如下:
五、总结
优点:
避免重复创建成本高的对象。
客户端可以直接获得对象副本,不需要知道如何创建。
可以动态添加或者修改复制逻辑。
缺点:
需要为每一个类配置一个克隆方法。
复制对象的成本也存在,特别是深拷贝。
需要注意隔离对象状态,避免相互影响。
应用场景:
对象的创建成本比较大,可以通过复制原型对象避免重复创建。
需要重复创建相似对象时可以考虑原型模式。
需要避免使用子类式继承改变对象结构时。
原型模式通过对象复制获取实例,避免重复创建开销大的对象,是一种快速获取对象副本的模式,但需要注意副本状态的一致性管理。