概念 :
原型模式 (Prototype Pattern)是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需依赖于昂贵的实例化过程。该模式基于原型实例生成新的对象,并且可以根据需要进行修改和定制。
特点 :
- 通过克隆已有对象来创建新对象,避免了重复的初始化过程。
- 可以动态地添加或删除已有的原型。
- 提供了一种简单、灵活和高效的方式来创建多个相似对象。
优点 :
- 减少了重复代码,提高了代码复用性。
- 避免了耗时的实例化操作,提升性能。
- 简化了对象创建过程,使得系统更加灵活。
缺点 :
- 需要为每个可变属性添加适当的克隆方法,并在每次修改时更新这些方法。
- 如果存在循环引用,则需要小心处理克隆过程中可能出现的无限循环问题。
适用场景 :
- 当一个系统需要独立于其具体类型创建多个相似但不同配置项或版本时,可以使用原型模式。
- 当一个系统需要动态地加载和配置类实例时,可以使用原型模式。
实现方式 :
浅拷贝:
直接对目标对象进行浅拷贝,复制基本数据类型的值和引用类型的地址。
实现原理
- 创建一个新对象,并将原始对象中所有的字段值复制给新对象。
- 对于基本数据类型,直接进行值复制。
- 对于引用类型,只复制其地址而不创建新的实例。
实现代码:
java
import java.util.ArrayList;
import java.util.List;
class Prototype implements Cloneable {
private int id;
private String name;
private List<String> list;
// 构造函数
public Prototype(int id, String name, List<String> list) {
this.id = id;
this.name = name;
this.list = list;
}
// Getter 和 Setter
public void setList(List<String> list) {
this.list = list;
}
public List<String> getList(){
return this.list;
}
@Override
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
}
public class Main {
public static void main(String[] args) {
List<String> originalList = new ArrayList<>();
originalList.add("item1");
originalList.add("item2");
Prototype originalObject = new Prototype(1, "Original", originalList);
try {
Prototype clonedObject = originalObject.clone();
System.out.println(originalObject == clonedObject); // 输出 false
System.out.println(originalObject.getList() == clonedObject.getList()); // 输出 true
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
在上述示例中,Prototype 类实现了 Cloneable 接口,并重写了 clone() 方法。通过调用 super.clone() 进行浅拷贝操作,返回一个新的克隆对象。
接下来我们创建了一个原始对象 originalObject,并进行浅拷贝得到克隆对象 clonedObject。可以观察到两个对象不相等(因为引用地址不同),但是它们共享相同的引用类型字段(即列表),所以对列表的修改会同时影响两个对象。
存在问题
由于浅拷贝仅仅是对引用进行了简单地复制操作,并没有创建全新独立的副本,因此可能会导致以下问题:
- 如果被复制的引用类型发生改变,那么克隆后得到的副本也会受到影响。
- 在多层嵌套结构中,如果某个属性发生改变,则克隆后得到副本中对应属性也会改变。
深拷贝:
对目标对象进行深拷贝,复制基本数据类型的值和引用类型的整个对象。
实现原理
- 创建一个新对象,并将原始对象中所有字段值复制给新对象。
- 对于基本数据类型,直接进行值复制。
- 对于引用类型,在克隆过程中创建该引用类型的全新副本,并将副本赋值给新对象。
实现代码
java
import java.util.ArrayList;
import java.util.List;
class Prototype implements Cloneable {
private int id;
private String name;
private List<String> list;
// 构造函数
public Prototype(int id, String name, List<String> list) {
this.id = id;
this.name = name;
this.list = list;
}
// Getter 和 Setter
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
@Override
public Prototype clone() throws CloneNotSupportedException {
Prototype clonedObject = (Prototype) super.clone();
clonedObject.list = new ArrayList<>(this.list); // 创建全新副本
return clonedObject;
}
}
public class Main {
public static void main(String[] args) {
List<String> originalList = new ArrayList<>();
originalList.add("item1");
originalList.add("item2");
Prototype originalObject = new Prototype(1, "Original", originalList);
try {
Prototype clonedObject = originalObject.clone();
System.out.println(originalObject == clonedObject); // 输出 false
System.out.println(originalObject.getList() == clonedObject.getList()); // 输出 false
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
在上述示例中,Prototype 类实现了 Cloneable 接口,并重写了 clone() 方法。通过调用 super.clone() 进行浅拷贝操作,然后对引用类型字段进行深层次克隆。
接下来我们创建了一个原始对象 originalObject,并进行深拷贝得到克隆对象 clonedObject。可以观察到两个对象完全独立(因为引用地址不同),它们的列表也是独立的副本,所以对列表的修改不会相互影响。
深拷贝虽然解决了浅拷贝可能带来的问题,但也存在以下问题:
- 如果被复制的引用类型内部还包含其他引用类型,则需要递归地进行深层次克隆操作。
- 某些类可能无法被序列化或反序列化而导致无法使用标准方法实现深拷贝。
原型注册表
使用原型管理器来存储和获取原型实例,可以通过名称或标识符查找并克隆相应的原型。
实现原理
- 创建一个原型注册表类(通常为单例),用于存储和管理多个不同类型的原型对象。
- 在注册表中添加方法,用于向注册表中添加、删除或获取指定名称的原型对象。
- 当客户端需要创建新对象时,可以通过调用注册表中相应名称的方法来获取对应的克隆副本。
实现代码
java
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
class Prototype implements Cloneable {
private int id;
private String name;
private List<String> list;
// 构造函数
public Prototype(int id, String name, List<String> list) {
this.id = id;
this.name = name;
this.list = list;
}
// Getter 和 Setter
public void setName(String name){
this.name = name;
}
public String getName() {
return this.name;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
@Override
public Prototype clone() throws CloneNotSupportedException {
Prototype clonedObject = (Prototype) super.clone();
clonedObject.list = new ArrayList<>(this.list); // 创建全新副本
return clonedObject;
}
}
class PrototypeRegistry {
private static PrototypeRegistry instance;
private Map<String, Prototype> prototypes;
private PrototypeRegistry() {
prototypes = new HashMap<>();
}
public static synchronized PrototypeRegistry getInstance() {
if (instance == null) {
instance = new PrototypeRegistry();
}
return instance;
}
public void addPrototype(String name, Prototype prototype) {
prototypes.put(name, prototype);
}
public void removePrototype(String name) {
prototypes.remove(name);
}
public Prototype getPrototype(String name) throws CloneNotSupportedException {
Prototype prototype = prototypes.get(name);
if (prototype != null) {
return prototype.clone();
}
return null;
}
}
public class Main {
public static void main(String[] args) {
// 创建原始对象并添加到注册表
List<String> originalList = new ArrayList<>();
originalList.add("item1");
originalList.add("item2");
Prototype originalObject = new Prototype(1, "Original", originalList);
PrototypeRegistry registry = PrototypeRegistry.getInstance();
registry.addPrototype("object1", originalObject);
// 从注册表获取副本并进行修改
try {
Prototype clonedObject = registry.getPrototype("object1");
if (clonedObject != null) {
clonedObject.setName("Cloned");
System.out.println(originalObject.getName()); // 输出 "Original"
System.out.println(clonedObject.getName()); // 输出 "Cloned"
System.out.println(originalObject.getList() == clonedObject.getList()); // 输出 false
}
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
在深拷贝实现代码的基础上,我们增加了PrototypeRegistry 类实现了原型注册表的功能。通过使用单例模式保证只有一个注册表实例,并使用 HashMap 存储原型对象。然后创建了一个原始对象 originalObject 并将其添加到注册表中。然后通过调用 getPrototype() 方法从注册表中获取副本,并对副本进行修改。可以观察到两个对象是独立的,对一个对象的修改不会影响到另一个对象。
序列化与反序列化
将对象序列化为字节流,然后再反序列化为新对象。
实现原理
- 被克隆的对象必须实现 Serializable 接口。
- 序列化:使用 ObjectOutputStream 将对象写入输出流,并将其转换为字节数组。
- 反序列化:使用 ObjectInputStream 从输入流中读取字节数组,并将其转换回原始对象。
实现代码
java
import java.io.*;
import java.util.ArrayList;
import java.util.List;
class Prototype implements Serializable {
private int id;
private String name;
private List<String> list;
// 构造函数
public Prototype(int id, String name, List<String> list) {
this.id = id;
this.name = name;
this.list = list;
}
// Getter 和 Setter
public List<String> getList() {
return list;
}
public Prototype deepClone() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (Prototype) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
public class Main {
public static void main(String[] args) {
List<String> originalList = new ArrayList<>();
originalList.add("item1");
originalList.add("item2");
Prototype originalObject = new Prototype(1, "Original", originalList);
Prototype clonedObject = originalObject.deepClone();
System.out.println(originalObject == clonedObject); // 输出 false
System.out.println(originalObject.getList() == clonedObject.getList()); // 输出 false
}
}
在上述示例中,Prototype 类实现了 Serializable 接口,并提供了一个 deepClone() 方法来进行深拷贝。在该方法中,首先将对象写入输出流并转换为字节数组,然后再从输入流中读取字节数组并转换回对象。然后我们创建了一个原始对象 originalObject 并通过调用 deepClone() 方法进行深拷贝得到副本对象 clonedObject。可以观察到两个对象是独立的(因为引用地址不同),它们的列表也是独立的副本,所以对列表的修改不会相互影响。需要注意的是当被克隆的类包含不可被序列化或反序列化的字段(如线程、文件句柄等),则需要采取额外措施来处理这些字段。