1、简述
在软件开发中,有时候我们需要创建许多相似但不完全相同的对象,这时候使用原型模式就显得非常有用。原型模式是一种创建型设计模式,它允许我们通过复制现有对象来创建新对象,而无需从头开始构建。本文将深入探讨 Java 中的原型模式,解释其原理、用法以及常见的应用场景。
2、原理
原型模式的核心思想是通过克隆(拷贝)现有对象来创建新的对象。这种克隆可以分为浅克隆和深克隆两种形式:
- 浅克隆(Shallow Clone):只复制对象本身,而不复制对象的引用类型属性。即新对象和原对象共享引用类型属性。
- 深克隆(Deep Clone):复制对象本身以及对象的引用类型属性,创建全新的对象,新对象和原对象的引用类型属性不共享。
Java 中实现原型模式的方式主要有两种:通过实现 Cloneable 接口和通过序列化。
3、浅拷贝
Java 提供了 Cloneable 接口,该接口表示一个类支持被复制(克隆)。如果一个类实现了 Cloneable 接口,那么它就可以使用 clone() 方法进行对象的克隆。下面是一个简单的示例:
public class Prototype implements Cloneable {
private String data;
/**
* 引用Teacher 对象
*/
private Teacher teacher;
public Prototype(String data) {
this.data = data;
}
@Override
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
}
引用的Teacher对象实例:
public class Teacher {
private String data;
public Teacher(String data) {
this.data = data;
}
@Override
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
通过使用main函数示例输出:
public class CloneMain {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher("Teacher Data");
Prototype prototype = new Prototype("Original Data");
teacher.setData("Teacher Data");
prototype.setTeacher(teacher);
Prototype clonedPrototype = prototype.clone();
clonedPrototype.setData("Original Data1");
clonedPrototype.getTeacher().setData("Teacher Data2");
System.out.println("原来对象:" + prototype.getData()); // 输出:Original Data
System.out.println("引用原来对象:" + prototype.getTeacher().getData()); // 输出:Teacher Data2
System.out.println("拷贝对象:" + clonedPrototype.getData()); // 输出:Original Data1
System.out.println("引用拷贝对象:" + clonedPrototype.getTeacher().getData()); // 输出:Teacher Data2
}
}
通过执行控制台输出结果:
原来对象:Original Data
引用原来对象:Teacher Data2
拷贝对象:Original Data1
引用拷贝对象:Teacher Data2
观察以上运行结果,我们可以发现:在我们给Prototype 拷贝对象 修改老师的时候,Prototype 原型的老师也跟着被修改了。这就是浅拷贝带来的问题。
4、深拷贝
另一种实现深克隆的方法是通过序列化和反序列化。通过将对象写入输出流,然后再从输入流中读取对象,可以创建一个新的对象,这个过程中会创建对象的副本。下面是一个示例:
4.1 使用序列化和反序列化
通过将对象写入输出流,然后再从输入流中读取对象,可以创建一个新的对象,这个过程中会创建对象的副本。
import java.io.*;
public class DeepPrototype implements Serializable {
private String data;
/**
* 引用Teacher 对象
*/
private Teacher teacher;
public DeepPrototype(String data) {
this.data = data;
}
public DeepPrototype deepClone() throws IOException, ClassNotFoundException {
// 写入输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(this);
// 从输入流中读取对象
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
return (DeepPrototype) objectInputStream.readObject();
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
}
引用的Teacher对象实例也要实现序列化Serializable:
public class Teacher implements Serializable {
private String data;
public Teacher(String data) {
this.data = data;
}
@Override
public Prototype clone() throws CloneNotSupportedException {
return (Prototype) super.clone();
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
通过使用main函数示例输出:
public class CloneMain {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Teacher teacher = new Teacher("Teacher Data");
DeepPrototype prototype = new DeepPrototype("Original Data");
teacher.setData("Teacher Data");
prototype.setTeacher(teacher);
DeepPrototype clonedPrototype = prototype.deepClone();
clonedPrototype.setData("Original Data1");
clonedPrototype.getTeacher().setData("Teacher Data2");
System.out.println("原来对象:" + prototype.getData()); // 输出:Original Data
System.out.println("引用原来对象:" + prototype.getTeacher().getData()); // 输出:Teacher Data
System.out.println("拷贝对象:" + clonedPrototype.getData()); // 输出:Original Data2
System.out.println("引用拷贝对象:" + clonedPrototype.getTeacher().getData()); // 输出:Teacher Data2
}
}
通过执行控制台输出结果:
原来对象:Original Data
引用原来对象:Teacher Data
拷贝对象:Original Data1
引用拷贝对象:Teacher Data2
4.2 使用 JSON 序列化和反序列化
将对象转换为 JSON 格式的字符串,然后再从字符串中反序列化出对象。这种方法需要使用一个 JSON 库,如 Jackson、Gson 等。以下是一个示例:
import com.fasterxml.jackson.databind.ObjectMapper;
public class DeepCopyUtils {
private static final ObjectMapper objectMapper = new ObjectMapper();
public static <T> T deepCopy(T obj, Class<T> clazz) throws IOException {
String json = objectMapper.writeValueAsString(obj);
return objectMapper.readValue(json, clazz);
}
}
4.3 使用 BeanUtils
Apache Commons BeanUtils 库提供了 BeanUtils.cloneBean() 方法,可以复制对象的属性到新对象中。这种方法需要确保对象的属性都是可序列化的。以下是一个示例:
import org.apache.commons.beanutils.BeanUtils;
public class DeepCopyUtils {
public static <T> T deepCopy(T obj) throws ReflectiveOperationException {
return (T) BeanUtils.cloneBean(obj);
}
}
你可以使用 DeepCopyUtils.deepCopy() 方法来实现深拷贝。
5、应用场景
原型模式在开源框架中的应用也非常广泛。例如 Spring 中,@Service 默认都是单例的。用了私有全局变量,若不想影响下次注入或每次上下文获取 bean,就需要用到原型模式,我们可以通过以下注解来实现,@Scope(“prototype”)。
- 当对象的创建成本较高,但需要频繁创建相似对象时,可以使用原型模式。
- 当需要避免构造函数重复调用,而又需要获得新对象时,可以使用原型模式。
- 当对象的结构比较复杂,无法直接使用 new 关键字创建对象时,可以使用原型模式。
6、总结
原型模式是一种创建型设计模式,通过复制现有对象来创建新对象,能够有效地减少对象的创建成本,提高代码的可复用性和可维护性。在 Java 中,可以通过实现 Cloneable 接口或者序列化来实现原型模式。通过深入理解原型模式的原理和用法,我们可以更好地应用它解决实际的软件设计和开发问题。