文章目录
- 一、什么是原型模式
- 二、原型模式实现方式
- 1、传统方式
- 2、原型模式
- 熟悉浅拷贝和深拷贝
- 浅拷贝实现对象克隆
- 深拷贝实现对象克隆
一、什么是原型模式
原型模式: 用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
对于熟悉 JavaScript 语言的前端程序员来说,原型模式是一种比较常用的开发模式。这是因为,有别于 Java、C++ 等基于类的面向对象编程语言,JavaScript 是一种基于原型的面向对象编程语言。即便 JavaScript 现在也引入了类的概念,但它也只是基于原型的语法糖而已。不过,如果你熟悉的是 Java、C++ 等这些编程语言,那在实际的开发中,就很少用到原型模式了。
二、原型模式实现方式
1、传统方式
public class Student {
private String name;
private int age;
// get set
}
public static void main(String[] args) {
//传统的方法
Student student = new Student("tom", 1);
Student student2 = new Student(student.getName(), student.getAge());
Student student3 = new Student(student.getName(), student.getAge());
//....
System.out.println(student2);
System.out.println(student3);
//...
}
传统的方式的优缺点:
优点是比较好理解,简单易操作。
在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活
2、原型模式
原型模式(Prototype 模式)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()。
熟悉浅拷贝和深拷贝
原型模式主要用于对象的复制,复制的类需要具备以下两个条件:
1)实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出 CloneNotSupportedException异常。
2)重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,需要拷贝的类需要将clone方法的作用域修改为public类型。
注:Object类的clone方法只会拷贝java中的8中基本类型以及他们的封装类型,另外还有String类型。对于数组、容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝。
浅拷贝实现对象克隆
浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值(仅对于简单的值类型数据),而所有的对其他对象的引用都仍然指向原来的对象。换言之,只负责克隆按值传递的数据(比如:基本数据类型、String类型)。
public class Student implements Cloneable {
private String name;
private int age;
private Address addr;
public Student(String name, int age, Address addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
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 Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", addr=" + addr +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Address implements Cloneable {
private String addrName;
public Address(String addrName) {
this.addrName = addrName;
}
public String getAddrName() {
return addrName;
}
public void setAddrName(String addrName) {
this.addrName = addrName;
}
@Override
public String toString() {
return "Address{" +
"addrName='" + addrName + '\'' +
'}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student("zhangsan", 13, new Address("China"));
Student clone = (Student) student.clone();
System.out.println(student);
System.out.println(clone);
student.setName("lisi");
student.getAddr().setAddrName("Canada");
System.out.println(student);
System.out.println(clone);
}
}
运行结果:
Student{name=‘zhangsan’, age=13, addr=Address{addrName=‘China’}}
Student{name=‘zhangsan’, age=13, addr=Address{addrName=‘China’}}
Student{name=‘lisi’, age=13, addr=Address{addrName=‘Canada’}}
Student{name=‘zhangsan’, age=13, addr=Address{addrName=‘Canada’}}
我们发现,Java的clone,只是单纯的浅拷贝,对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
这里我们修改了原Student的Address中的值,克隆过的Student中的Address中的值也相应的改变了。
深拷贝实现对象克隆
1、
把上面的Student中clone方法改造一下,即可以实现深拷贝:
@Override
public Object clone() throws CloneNotSupportedException {
Student newStu = (Student) super.clone();
newStu.addr = (Address) this.getAddr().clone();
return newStu;
}
拷贝的时候,将对象中的引用数据也拷贝一份即可。
2、可以使用序列化 + 反序列化的方式实现深拷贝:
public Object deepCopy(Object object) {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(object);
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return oi.readObject();
}
当然,序列化反序列化的方式有很多,这里就不一一列举了。