原型模式(Prototype Pattern)
- 概述:
原型模式(Prototype Pattern)是创建型模式,用于当创建对象的成本高时,通过复制一个现有对象来创建一个新对象,从而避免创建一个新对象的 。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。 - 结构:
- 抽象原型类:定义了一个克隆自身的接口,即规定了具体原型对象必须实现的clone()方法。
- 具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象。
- 客户端类:使用具体原型类中的clone()方法来克隆新的对象。
- 实现:
原型模式的克隆分为:浅克隆和深克隆。
- 浅克隆创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,任指向原有属性所指向的对象的内存地址。
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不会指向原有对象地址。
- 注意:Java中的Object类提供了clone()方法,该方法返回一个对象,该对象是当前对象的一个副本。
- 浅克隆:调用Object类的clone()方法。
- 深克隆:
- 实现Cloneable接口。
- 重写clone()方法。
- 在clone()方法中,使用super.clone()方法创建一个新对象,然后将当前对象的属性值复制到新对象中。
- 如果属性是引用类型,需要递归调用clone()方法。
在Java中,原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而不是通过新建类实例的方式。在实现原型模式时,我们通常会使用clone()
方法,而复制对象时就会涉及到浅克隆(Shallow Clone)和深克隆(Deep Clone)的概念。
浅克隆(Shallow Clone)
浅克隆是指当复制一个对象时,对于基本数据类型的成员变量,会直接复制其值;而对于引用类型的成员变量,则只复制其引用地址(也就是内存地址),而不会复制引用所指向的实际对象。因此,在浅克隆中,原始对象和克隆对象中的引用类型成员变量将指向同一个对象。
在Java中,通过实现Cloneable
接口并重写Object
类的clone()
方法来实现浅克隆。Object
类的clone()
方法默认实现就是浅克隆。
示例代码:
class Address { |
深克隆(Deep Clone)
深克隆是指复制对象时,不仅复制对象本身,而且递归复制对象所引用的其他对象。因此,深克隆会复制整个对象网络,使得原始对象和克隆对象之间互不影响,它们拥有各自独立的内存空间。
实现深克隆的方式有多种:
- 重写
clone()
方法,并在其中对引用类型进行递归克隆(需要引用类型也实现Cloneable
接口并重写clone()
方法)。 - 使用序列化(Serialization)和反序列化(Deserialization)来实现深克隆。将对象写入到字节流中,然后再从字节流中读取回来,这样会创建一个完全独立的副本。
示例代码(通过重写clone方法实现深克隆):class Address implements Cloneable {
private String city;
public Address(String city) {
this.city = city;
}
// getter and setter
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable {
private String name;
private Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
// getter and setter
protected Object clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
// 对引用类型单独克隆
cloned.address = (Address) address.clone();
return cloned;
}
}
// 使用
Address address = new Address("Beijing");
Person p1 = new Person("Tom", address);
Person p2 = (Person) p1.clone();
// 此时,p1和p2的address引用指向不同的Address对象
System.out.println(p1.getAddress() == p2.getAddress()); // false
序列化实现深克隆
如果引用类型嵌套层次很深,或者引用类型没有实现Cloneable
接口,则可以通过序列化的方式实现深克隆。需要确保所有涉及的类都是可序列化的(实现Serializable
接口)。
示例代码:
import java.io.*; |
总结
- 浅克隆:复制基本数据类型的值,对于引用类型,只复制引用地址,因此原对象和克隆对象会共享引用类型的成员变量。修改其中一个对象的引用类型成员变量,另一个对象也会受到影响。
- 深克隆:复制基本数据类型的值,同时递归复制引用类型的对象。因此,原对象和克隆对象之间完全独立,互不影响。
在实际应用中,选择浅克隆还是深克隆取决于具体需求。如果对象的引用类型成员变量在创建后不会改变,或者你希望共享这些对象,那么浅克隆就足够了,并且效率更高。如果需要完全独立的副本,则应该使用深克隆。但要注意深克隆可能带来的性能问题,特别是在对象图很大的情况下。
关键区别总结
特性 | 浅克隆 | 深克隆 |
---|---|---|
引用类型复制 | 复制内存地址(共享对象) | 递归复制实际对象(完全独立) |
修改影响 | 修改引用类型成员会影响所有副本 | 修改引用类型成员不影响其他副本 |
实现复杂度 | 简单(默认clone() ) |
复杂(需递归处理所有引用类型) |
性能 | 高效(不创建新对象) | 较低(递归创建新对象) |
适用场景 | 引用类型不可变或无需隔离时 | 引用类型需完全隔离时 |
注意事项
String
的特殊性:
虽然String
是引用类型,但其不可变性(Immutable)使得浅克隆中修改String
值会创建新对象,不会影响原对象(行为类似深克隆)。但其他引用类型(如自定义类)仍需谨慎。- 深克隆的替代方案:
序列化(Serializable
)是另一种深克隆实现,但要求所有涉及的对象都实现Serializable
接口:public static <T> T deepClone(T obj) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
}