目录
一、原型模式的介绍
1、定义
原型模式用来创建重复的对象,即从一个对象再创建另一个对象,而且不需要知道任何创建的细节。这种模式就像克隆一样,以某个给定的对象为原型,复制出新的对象。就像动画片西游记的主题曲里唱的一样:拔一根毫毛,吹出猴万个。
在原型模式的结构中,主要包含以下角色:
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象。
- 访问类(客户端):使用具体原型类中的clone()方法来复制新的对象。
2、使用场景
- 资源优化。
- 类初始化需要消耗很多的资源,包括数据资源、硬件资源等。
- 对性能和安全有要求的场景。
- 通过new创建一个对象需要非常繁琐的数据准备或访问权限。
- 一个对象多个修改者的场景。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用
3、优缺点
优点:提高性能;逃避了构造函数的约束。
缺点:需要为每一个类配备一个克隆方法,如果要对已有类进行修改,就需要修改源代码,违背了OCP原则。
二、原型模式的实现
Java语言的Object类中提供了一个clone()方法,对于任何实现了Cloneable接口的类,都可以通过clone()方法完成对象的克隆。由于clone方法是由虚拟机直接复制内存块执行,所以在速度上比使用new的方式创建对象要快。
原型模式的UML类图如下:
1、浅拷贝
(1)数据类型的介绍:
- 如果数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,即把该属性值复制一份给新的对象。
- 如果数据类型是引用类型的成员变量,例如:数组、自定义类对象等,那么浅拷贝会进行引用传递,也就是只将该成员变量的引用值赋值给新的对象,从而使用两个对象的成员变量都指向同一个实例。
- String类型通过常量赋值时相当于基本数据类型,通过new关键字创建对象时便是引用数据类型。
(2)实现过程
如果要对一个Person类进行复制,可以让Person实现Cloneable接口,然后重写clone()方法,具体如下:
class Person implements Cloneable{
private String name;
private Integer age;
private String address;
public Person() {
}
public Person(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
@Override
protected Person clone(){
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return (Person)obj;
}
}
public class ProtoMode {
public static void main(String[] args) {
Person person = new Person("老大哥", 20, "陕西");
System.out.println(person);
System.out.println("==========");
Person clonePerson = person.clone();
System.out.println(clonePerson);
System.out.println(person == clonePerson);//false
}
}
上面的代码已经达到了克隆的目的。如果对比person和clonePerson的内存地址可以发现它们是不一样的。
(3)存在的问题
就像上面介绍的一样,如果成员变量是引用类型,那么复制后对象和原对象都指向同一个实例,那么其中一个对该内存地址中的数据进行修改会导致另外一个对象发生变化。这里我们可以简单验证一下:
场景描述:在Teacher类里面有一个自定义类对象Student,测试两种情况:
- 复制之后两个对象的Student是否指向同一个对象?
- 如果修改clonePerson的Student,是否引起person中数据变化?
class Student{
private String name;
private Integer age;
public Student() {}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Teacher implements Cloneable{
private String name;
private String address;
private Student student;
public Teacher() {
}
public Teacher(String name, String address, Student student) {
this.name = name;
this.address = address;
this.student = student;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", student=" + student +
'}';
}
@Override
protected Teacher clone(){
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return (Teacher) obj;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("张三", 18);
Teacher teacher = new Teacher("李明", "江苏", student);
System.out.println(teacher);
Teacher cloneTeacher = teacher.clone();
System.out.println(cloneTeacher);
//验证复制前后的student是否指向同一地址
System.out.println(teacher.getStudent() == cloneTeacher.getStudent());//true
cloneTeacher.getStudent().setName("李四");
System.out.println(teacher.getStudent().getName());//李四
System.out.println(cloneTeacher.getStudent().getName());//李四
}
}
通过测试可以发现确实存在数据被修改的情况,那么如何解决呢?接下来就来介绍深拷贝,能够避免这种情况的发生。
(4)构造函数的说明
在进行对象拷贝的时候构造函数是不会执行的,其原因在于拷贝是在堆中进行。因为在new的时候,JVM要进行类加载流程,此时会调用构造函数,最后生成的对象会放到堆中,而拷贝就是直接拷贝堆中的现成的二进制对象,然后重新一个分配内存块。来看下面的实例:
package mode;
class Person implements Cloneable{
private String name;
private Integer age;
private String address;
public Person() {
System.out.println("Person的构造函数");
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
@Override
protected Person clone(){
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return (Person)obj;
}
}
public class ProtoMode {
public static void main(String[] args) {
Person person = new Person();
Person clonePerson = person.clone();
}
}
结果输出:Person的构造函数。
这就证明了构造函数在对象Clone的时候不会被执行。
2、深拷贝
深拷贝不仅复制对象的所有基本数据类型的成员变量值,而且会为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝。
(1)实现方式一:重写clone()方法实现深拷贝
对于浅拷贝中测试的代码场景,解决引用类型的拷贝问题,代码如下:
class Student implements Cloneable{
private String name;
private Integer age;
public Student() {}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
protected Student clone(){
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return (Student)obj;
}
}
class Teacher implements Cloneable{
private String name;
private String address;
private Student student;
public Teacher() {
}
public Teacher(String name, String address, Student student) {
this.name = name;
this.address = address;
this.student = student;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", student=" + student +
'}';
}
@Override
protected Teacher clone(){
Object obj = null;
try {
obj = super.clone();
this.student = student.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return (Teacher) obj;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("张三", 18);
Teacher teacher = new Teacher("李明", "江苏", student);
System.out.println(teacher);
Teacher cloneTeacher = teacher.clone();
System.out.println(cloneTeacher);
//验证复制前后的student是否指向同一地址
System.out.println("person和clonePerson的Student地址相同?\t\t" + (teacher.getStudent() == cloneTeacher.getStudent()));//false
System.out.println("修改学生数据:");
cloneTeacher.getStudent().setName("李四");
System.out.println("person中学生的姓名:" + teacher.getStudent().getName());//张三
System.out.println("clonePerson中学生的姓名:" + cloneTeacher.getStudent().getName());//李四
}
}
执行结果如下:
(2)实现方式二:通过序列化实现深拷贝
代码如下:
import java.io.*;
class Student implements Serializable {
private String name;
private Integer age;
public Student() {}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class Teacher implements Serializable{
private String name;
private String address;
private Student student;
public Teacher() {
}
public Teacher(String name, String address, Student student) {
this.name = name;
this.address = address;
this.student = student;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
", student=" + student +
'}';
}
public Object deepClone() throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
public class Test {
public static void main(String[] args) throws Exception{
Student student = new Student("张三", 18);
Teacher teacher = new Teacher("李明", "江苏", student);
System.out.println(teacher);
Teacher cloneTeacher = (Teacher) teacher.deepClone();
System.out.println(cloneTeacher);
//验证复制前后的student是否指向同一地址
System.out.println("person和clonePerson的Student地址相同?\t\t" + (teacher.getStudent() == cloneTeacher.getStudent()));//false
System.out.println("修改学生数据:");
cloneTeacher.getStudent().setName("李四");
System.out.println("person中学生的姓名:" + teacher.getStudent().getName());//张三
System.out.println("clonePerson中学生的姓名:" + cloneTeacher.getStudent().getName());//李四
}
}
测试结果如下:
说明:Serializable和Cloneable都是标识性接口,里面没有任何定义。