原型模式的结构与多种实现,包括实现Cloneable接口,并重写clone方法,和复制构造器实现深拷贝,浅拷贝,及使用Serializable实现深拷贝
Posted 阿啄debugIT
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了原型模式的结构与多种实现,包括实现Cloneable接口,并重写clone方法,和复制构造器实现深拷贝,浅拷贝,及使用Serializable实现深拷贝相关的知识,希望对你有一定的参考价值。
今天是10月24日,1024程序猿节!
1024程序猿节日由来:因程序员经常使用二进制,2的10次方=1024,所以10月24日也就成为了程序员的节日了......
什么是原型模式?
原型(Prototype)模式的定义:
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个,和原型相同或相似的新对象。
原型模式的结构与实现
由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
原型模式的结构,包含以下主要角色
- 抽象原型类(AbstractPrototype):规定了具体原型对象必须实现的接口。
- 具体原型类(concretePrototype):实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法,来复制新的对象。
原型实例指定了要创建的对象的种类。
用这种方式创建对象非常高效,根本无须知道对象创建的细节。
原型模式的优点:
- Java 自带的原型模式,基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
原型模式的缺点:
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类,都必须支持深克隆,实现起来,会比较麻烦。
因此,深克隆、浅克隆需要运用得当。
原型模式的克隆分为浅克隆和深克隆
深拷贝与浅拷贝有什么区别?直接代码gif对比展示
什么是浅克隆
创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
Subject主题类
@Data
public class Subject {
private String name;
public Subject(String name) {
this.name = name;
}
@Override
public String toString() {
return "[Subject: " + this.hashCode() + ",name:" + name + "]";
}
}
Student实现Cloneable接口,并重写clone方法
@Data
public class Student implements Cloneable {
//引用类型
private Subject subject;
//基础数据类型
private String name;
private int age;
/**
* 重写clone()方法
* @return
*/
@Override
public Object clone() {
//浅拷贝
try {
// 直接调用父类的clone()方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
ShallowCopy 浅拷贝测试
public class ShallowCopy {
public static void main(String[] args) {
Subject subject = new Subject("yuwen");
Student studentA = new Student();
studentA.setSubject(subject);
studentA.setName("Lynn");
studentA.setAge(20);
System.out.println("studentA:------------1--" + studentA.toString());
Student studentB = (Student) studentA.clone();
studentB.setName("Lily");
studentB.setAge(18);
System.out.println("studentB:------------1--" + studentB.toString());
Subject subjectB = studentB.getSubject();
subjectB.setName("lishi");
System.out.println("studentA:------------2--" + studentA.toString());
System.out.println("studentB:------------2--" + studentB.toString());
}
}
可以看出,Student经过浅拷贝以后,hashCode的值,发生变化;但是内部的属性的hashCode的值,没有变化;只有改变内部属性的值,主类,和子类才发生属性值不同……
浅拷贝并没有做到数据的100%分离,StudentA和StudentB共享同一个Subject对象,所以一个修改会影响到另一个。
浅拷贝总结:
- 使用默认的clone方法
- 对于原始数据域进行值拷贝
- 对于引用类型仅拷贝引用
- 执行快,效率高
- 不能做到数据的100%分离。
如果一个对象,只包含原始数据域,或者不可变对象域,推荐使用浅拷贝。
什么是深克隆
创建一个新对象,属性中引用的其他对象,也会被克隆,不再指向原有对象地址。
Subject主题类
@Data
public class Subject implements Cloneable {
private String name;
public Subject(String name) {
this.name = name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//Subject 如果也有引用类型的成员属性,也应该和 Student 类一样实现
return super.clone();
}
@Override
public String toString() {
return "[Subject: " + this.hashCode() + ",name:" + name + "]";
}
}
Student实现Cloneable接口,并重写clone方法
@Data
public class Student implements Cloneable {
//引用类型
private Subject subject;
//基础数据类型
private String name;
private int age;
/**
* 重写clone()方法
* @return
*/
@Override
public Object clone() {
//深拷贝
try {
// 直接调用父类的clone()方法
Student student = (Student) super.clone();
student.subject = (Subject) subject.clone();
return student;
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public String toString() {
return "[Student: " + this.hashCode() + ",subject:" + subject + ",name:" + name + ",age:" + age + "]";
}
}
DeepCopy深拷贝测试
public class DeepCopy {
public static void main(String[] args) {
Subject subject = new Subject("yuwen");
Student studentA = new Student();
studentA.setSubject(subject);
studentA.setName("Lynn");
studentA.setAge(20);
System.out.println("studentA:------------1--" + studentA.toString());
Student studentB = (Student) studentA.clone();
studentB.setName("Lily");
studentB.setAge(18);
System.out.println("studentB:------------1--" + studentB.toString());
Subject subjectB = studentB.getSubject();
subjectB.setName("lishi");
System.out.println("studentA:------------2--" + studentA.toString());
System.out.println("studentB:------------2--" + studentB.toString());
}
}
通过打出的日志,可以看出深拷贝的特点:
- 需要重写clone方法,不仅仅只调用父类的方法,还需调用属性的clone方法
- 做到了原对象与克隆对象之间100%数据分离
- 如果是对象存在引用类型的属性,建议使用深拷贝
- 深拷贝比浅拷贝要更加耗时,效率更低
当我们再次修改StudentB.Subject.name就不会影响到StudentA.Subject.name的值了,因为StudentA和StudentB各自拥有自己的Subject对象,因为做到了数据的100%隔离。
clone使用的场景
某个API需要提供一个List集合,但是又不希望调用者的修改影响到自身的变化,因此需要克隆一份对象,以此达到数据隔离的目的。
日常生产开发,应尽量避免clone的原因
- 通常情况下,实现接口,是为了表明类可以为它的客户做些什么,而Cloneable仅仅是一个标记接口,而且还改变了super超类中,受保护的方法的行为,是接口的一种极端非典型的用法,不值得效仿。
- Clone方法,约定及其脆弱,clone方法的Javadoc描述有点暧昧模糊。
“如下为 Java SE8的约定,clone方法创建,并返回该对象的一个拷贝”,
而拷贝的精确含义,取决于该对象的类。
按照惯例,返回的对象应该通过调用super.clone获得。
如果一个类及其所有超类(对象除外)都遵守此约定,则x.clone().getClass() == x.getClass()。按照约定,此方法返回的对象,应该独立于此对象(正在克隆)。
要实现这种独立性,在super.clone返回的对象之前,可能必须修改该返回对象的一个或多个字段。
通常,这意味着复制,构成被克隆对象内部“深层结构”的任何可变对象,并将对这些对象的引用替换为对副本的引用。
如果类仅包含基本字段或引用不可变对象,则通常情况下super.clone返回的对象中,不需要修改任何字段。
- 可变对象final域,在克隆方法中,如果我们需要对可变对象的final域也进行拷贝,由于final的限制,所以实际上是无法编译通过的。因此为了实现克隆,我们需要考虑,是否舍去该可变对象域的final关键字。
- 线程安全,如果你决定用线程安全的类,实现Cloneable接口,则需要保证它的clone方法同步工作。默认的Object.clone方法是没有做同步的。
总的来说,java中的clone方法,实际上并不是完善的,建议尽量避免使用。可以采用“复制构造器”替代clone方案。
复制构造器也可以实现对象的拷贝
复制构造器--实现浅拷贝
public class Car {
Wheel wheel;
String manufacturer;
public Car(Wheel wheel, String manufacturer) {
this.wheel = wheel;
this.manufacturer = manufacturer;
}
//浅拷贝copy constructor
public Car(Car car) {
this(car.wheel, car.manufacturer);
}
public static class Wheel {
String brand;
}
}
复制构造器--实现深拷贝
public class DeepCar {
DeepCar.Wheel wheel;
String manufacturer;
public DeepCar(DeepCar.Wheel wheel, String manufacturer) {
this.wheel = wheel;
this.manufacturer = manufacturer;
}
//深拷贝copy constructor
public DeepCar(DeepCar car) {
Wheel wheel = new Wheel();
wheel.brand = car.wheel.brand;
this.wheel = wheel;
this.manufacturer = car.manufacturer;
}
public static class Wheel {
String brand;
}
//深拷贝
//使用Serializable实现深拷贝
public static DeepCar newInstance(DeepCar car) {
return new DeepCar(car);
}
}
复制构造器--深拷贝与浅拷贝的区别
使用Serializable实现深拷贝
运行结果
由输出结果,可以看出,copy对象的child值修改,不影响example对象的child值,即使用序列化可以实现对象的深拷贝。
深度总结原型模式
虽然面对一个复杂的类层次,且系统必须从其中的许多类型中,创建新对象时,用原型模式相当复杂,但是也是必要选择。毕竟原型模式,可以向客户隐藏制造新实例的复杂性;及提供让客户能够产生未知类型对象的选项;在某些环境下,复制对象比创建新对象,更有效。
当然使用深拷贝,浅拷贝不要只考虑clone方法的使用,还有构造方法,及序列化来更好的实现原型模式的拷贝问题!
在1年1度的1024程序员节日里,祝所有的程序员们“程”风破浪、“码”出未来、横扫BUG。
以上是关于原型模式的结构与多种实现,包括实现Cloneable接口,并重写clone方法,和复制构造器实现深拷贝,浅拷贝,及使用Serializable实现深拷贝的主要内容,如果未能解决你的问题,请参考以下文章
原型模式的结构与多种实现,包括实现Cloneable接口,并重写clone方法,和复制构造器实现深拷贝,浅拷贝,及使用Serializable实现深拷贝