文章目录
1. 什么是原型模式?
原型模式是一种创建型设计模式,它允许我们通过复制(克隆)现有对象来创建新对象,而不是通过使用构造函数创建。原型模式的核心思想是基于现有对象创建新的对象,而不是从零开始创建。
这种模式特别适用于创建对象成本较高的场景,或者需要创建大量相似对象的情况。通过克隆现有对象,可以避免重新执行初始化过程,从而提高性能。
2. 为什么需要原型模式?
在以下情况下,原型模式特别有用:
- 当创建对象的过程很昂贵或复杂时(如需要进行数据库操作或文件I/O)
- 当需要创建的对象与现有对象差别不大时
- 当需要避免使用构造函数创建对象的限制时
- 当需要保存对象状态并在需要时恢复时
- 当系统需要独立于对象的创建方式时
3. 原型模式的结构
原型模式通常包含以下角色:
- 原型接口(Prototype):声明克隆自身的方法
- 具体原型(Concrete Prototype):实现克隆方法的类
- 客户端(Client):使用原型实例创建新对象的类
4. 原型模式的基本实现
4.1 基础示例:简单的原型模式
首先,我们定义一个原型接口:
// 原型接口
public interface Prototype {
Prototype clone();
}
然后,实现具体原型类:
// 具体原型类
public class ConcretePrototype implements Prototype {
private String field;
public ConcretePrototype(String field) {
this.field = field;
}
// 用于测试的getter和setter
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
@Override
public Prototype clone() {
return new ConcretePrototype(this.field);
}
@Override
public String toString() {
return "ConcretePrototype [field=" + field + "]";
}
}
最后,客户端代码:
// 客户端代码
public class Client {
public static void main(String[] args) {
// 创建原型对象
ConcretePrototype prototype = new ConcretePrototype("原始值");
System.out.println("原型对象: " + prototype);
// 克隆原型对象
ConcretePrototype clone = (ConcretePrototype) prototype.clone();
System.out.println("克隆对象: " + clone);
// 修改克隆对象的属性
clone.setField("修改后的值");
System.out.println("修改后的克隆对象: " + clone);
System.out.println("原型对象: " + prototype); // 原型对象不受影响
}
}
输出结果:
原型对象: ConcretePrototype [field=原始值]
克隆对象: ConcretePrototype [field=原始值]
修改后的克隆对象: ConcretePrototype [field=修改后的值]
原型对象: ConcretePrototype [field=原始值]
4.2 使用Java的Cloneable接口
Java提供了Cloneable
接口和Object.clone()
方法来支持原型模式。下面是使用Java内置机制的例子:
// 使用Java的Cloneable接口实现原型模式
public class Person implements Cloneable {
private String name;
private int age;
private String address;
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
// Getters and Setters
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 String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
@Override
public Person clone() {
try {
// 调用Object的clone方法
return (Person) super.clone();
} catch (CloneNotSupportedException e) {
// 实现了Cloneable接口的类不应该抛出这个异常
throw new AssertionError("这不应该发生", e);
}
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
使用示例:
public class CloneableDemo {
public static void main(String[] args) {
// 创建原型对象
Person original = new Person("张三", 30, "北京市海淀区");
System.out.println("原始对象: " + original);
// 克隆对象
Person clone = original.clone();
System.out.println("克隆对象: " + clone);
// 修改克隆对象
clone.setName("李四");
clone.setAge(25);
clone.setAddress("上海市浦东新区");
// 查看修改后的结果
System.out.println("修改后的克隆对象: " + clone);
System.out.println("原始对象: " + original); // 原始对象不变
}
}
输出结果:
原始对象: Person [name=张三, age=30, address=北京市海淀区]
克隆对象: Person [name=张三, age=30, address=北京市海淀区]
修改后的克隆对象: Person [name=李四, age=25, address=上海市浦东新区]
原始对象: Person [name=张三, age=30, address=北京市海淀区]
5. 深拷贝与浅拷贝
在原型模式中,有两种类型的复制:
5.1 浅拷贝(Shallow Copy)
浅拷贝只复制对象的基本属性,对于引用类型,只复制引用而不复制引用指向的对象。Java的Object.clone()
方法默认执行浅拷贝。
// 包含引用类型的类
public class Employee implements Cloneable {
private String name;
private Department department; // 引用类型
public Employee(String name, Department department) {
this.name = name;
this.department = department;
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
// 浅拷贝
@Override
public Employee clone() {
try {
return (Employee) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
@Override
public String toString() {
return "Employee [name=" + name + ", department=" + department + "]";
}
}
// 引用类型
public class Department {
private String name;
public Department(String name) {
this.name = name;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public String toString() {
return "Department [name=" + name + "]";
}
}
浅拷贝示例:
public class ShallowCopyDemo {
public static void main(String[] args) {
// 创建部门
Department hr = new Department("人力资源部");
// 创建员工
Employee original = new Employee("张三", hr);
System.out.println("原始员工: " + original);
// 浅拷贝
Employee clone = original.clone();
System.out.println("克隆员工: " + clone);
// 修改克隆对象的引用类型属性
clone.getDepartment().setName("财务部");
// 查看修改后的结果
System.out.println("修改后的克隆员工: " + clone);
System.out.println("原始员工: " + original); // 原始对象的引用类型也被修改了
}
}
输出结果:
原始员工: Employee [name=张三, department=Department [name=人力资源部]]
克隆员工: Employee [name=张三, department=Department [name=人力资源部]]
修改后的克隆员工: Employee [name=张三, department=Department [name=财务部]]
原始员工: Employee [name=张三, department=Department [name=财务部]]
注意:修改克隆对象的引用类型属性会影响原始对象。
5.2 深拷贝(Deep Copy)
深拷贝不仅复制对象本身,还复制对象包含的所有引用类型的属性。有两种常见的实现方式:
5.2.1 通过递归复制实现深拷贝
// 实现Cloneable接口的引用类型
public class Department implements Cloneable {
private String name;
public Department(String name) {
this.name = name;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
protected Department clone() {
try {
return (Department) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
@Override
public String toString() {
return "Department [name=" + name + "]";
}
}
// 深拷贝实现
public class Employee implements Cloneable {
private String name;
private Department department;
public Employee(String name, Department department) {
this.name = name;
this.department = department;
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
// 深拷贝
@Override
public Employee clone() {
try {
Employee cloned = (Employee) super.clone();
// 对引用类型也进行克隆
cloned.department = this.department.clone();
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
@Override
public String toString() {
return "Employee [name=" + name + ", department=" + department + "]";
}
}
5.2.2 通过序列化实现深拷贝
使用序列化和反序列化实现深拷贝是一种更通用的方法,特别适用于对象层次较深的情况:
import java.io.*;
// 必须实现Serializable接口
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public Department(String name) {
this.name = name;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override
public String toString() {
return "Department [name=" + name + "]";
}
}
// 实现Serializable接口
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Department department;
public Employee(String name, Department department) {
this.name = name;
this.department = department;
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
// 通过序列化实现深拷贝
public Employee deepCopy() {
try {
// 写入字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 从字节流读取
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Employee) ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return "Employee [name=" + name + ", department=" + department + "]";
}
}
使用序列化深拷贝的示例:
public class DeepCopySerializationDemo {
public static void main(String[] args) {
// 创建部门
Department hr = new Department("人力资源部");
// 创建员工
Employee original = new Employee("张三", hr);
System.out.println("原始员工: " + original);
// 深拷贝
Employee clone = original.deepCopy();
System.out.println("克隆员工: " + clone);
// 修改克隆对象的引用类型属性
clone.getDepartment().setName("财务部");
// 查看修改后的结果
System.out.println("修改后的克隆员工: " + clone);
System.out.println("原始员工: " + original); // 原始对象不受影响
}
}
输出结果:
原始员工: Employee [name=张三, department=Department [name=人力资源部]]
克隆员工: Employee [name=张三, department=Department [name=人力资源部]]
修改后的克隆员工: Employee [name=张三, department=Department [name=财务部]]
原始员工: Employee [name=张三, department=Department [name=人力资源部]]
6. 原型模式的实际应用场景
6.1 数据对象的复制
当需要创建大量相似但略有差异的数据对象时,原型模式非常有用:
public class DataObject implements Cloneable {
private String id;
private String name;
private String description;
private Date creationDate;
// 构造函数和其他初始化代码...
@Override
protected DataObject clone() {
try {
DataObject clone = (DataObject) super.clone();
// 对日期进行深拷贝
clone.creationDate = (Date) this.creationDate.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
// 创建一个带有不同ID的新实例
public DataObject cloneWithNewId(String newId) {
DataObject clone = this.clone();
clone.id = newId;
return clone;
}
}
6.2 对象的缓存
原型模式可以用于实现对象缓存,避免重复创建开销大的对象:
import java.util.HashMap;
import java.util.Map;
// 原型管理器
public class PrototypeManager {
private static Map<String, Prototype> prototypes = new HashMap<>();
// 注册原型
public static void register(String key, Prototype prototype) {
prototypes.put(key, prototype);
}
// 获取原型的克隆
public static Prototype getPrototype(String key) {
Prototype prototype = prototypes.get(key);
if (prototype != null) {
return prototype.clone();
}
return null;
}
}
// 原型接口
public interface Prototype {
Prototype clone();
}
// 具体原型
public class Document implements Prototype {
private String content;
private String format;
public Document(String content, String format) {
this.content = content;
this.format = format;
// 假设初始化过程非常耗时
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public Document clone() {
return new Document(this.content, this.format);
}
// 其他方法...
}
使用示例:
public class PrototypeManagerDemo {
public static void main(String[] args) {
// 初始化原型
Document wordDoc = new Document("示例内容", "Word");
Document pdfDoc = new Document("示例内容", "PDF");
// 注册原型
PrototypeManager.register("word", wordDoc);
PrototypeManager.register("pdf", pdfDoc);
// 使用原型创建对象
long start = System.currentTimeMillis();
Document doc1 = (Document) PrototypeManager.getPrototype("word");
Document doc2 = (Document) PrototypeManager.getPrototype("pdf");
long end = System.currentTimeMillis();
System.out.println("克隆两个文档耗时: " + (end - start) + "毫秒"); // 几乎不耗时
// 对比直接创建
start = System.currentTimeMillis();
Document doc3 = new Document("示例内容", "Word");
Document doc4 = new Document("示例内容", "PDF");
end = System.currentTimeMillis();
System.out.println("直接创建两个文档耗时: " + (end - start) + "毫秒"); // 约2000毫秒
}
}
6.3 Java中的实际应用
Java标准库中也有一些类使用了原型模式:
- Java集合框架中的
clone()
方法 - Object类提供的
clone()
方法
7. 原型模式与其他设计模式的区别
7.1 原型模式 vs 工厂模式
原型模式 | 工厂模式 |
---|---|
通过复制现有对象创建新对象 | 通过工厂类创建对象 |
不需要关心类的具体实现细节 | 需要知道具体的产品类 |
适合创建成本高的对象 | 适合创建多种不同类型的对象 |
7.2 原型模式 vs 建造者模式
原型模式 | 建造者模式 |
---|---|
通过克隆创建对象 | 分步骤创建复杂对象 |
基于现有对象创建 | 从零开始构建对象 |
适合创建相似对象 | 适合构建复杂对象 |
8. 原型模式的优缺点
8.1 优点
- 减少对象创建的成本,特别是创建过程复杂或耗时的情况
- 隐藏对象创建的细节
- 允许在运行时添加和删除产品
- 提供了一种快速创建复杂对象的方法
- 可以避免构造函数的限制,如创建不可变对象的多个变体
8.2 缺点
- 对象包含循环引用时的克隆可能很复杂
- 深拷贝实现复杂,特别是对象结构较深时
- 克隆包含引用类型的对象时需要格外小心(浅拷贝问题)
- 对于一些有状态的对象,可能需要重置状态
9. 何时使用原型模式?
以下情况适合使用原型模式:
- 当创建对象的代价较大,且创建的对象之间差异较小时
- 当需要避免构建与产品类层次平行的工厂类层次时
- 当对象的类在运行时才确定时
- 当系统需要独立于产品如何创建、组合和表示时
- 当需要保存对象的状态,并在将来需要恢复到这个状态时
10. 常见问题及解决方案
10.1 问题:如何处理深拷贝中的循环引用?
解决方案:使用哈希表记录已经克隆过的对象,避免重复克隆。
import java.util.HashMap;
import java.util.Map;
public class DeepCopyUtil {
private static Map<Object, Object> clonedObjects = new HashMap<>();
public static <T> T deepCopy(T object) {
// 如果对象已经被克隆,直接返回克隆的对象
if (clonedObjects.containsKey(object)) {
return (T) clonedObjects.get(object);
}
// 这里实现深拷贝逻辑
// ...
// 将克隆对象添加到哈希表中
clonedObjects.put(object, clonedObject);
return clonedObject;
}
}
10.2 问题:如何克隆不可变对象?
解决方案:对于不可变对象,可以直接返回对象本身,不需要克隆。
public class ImmutableObject implements Cloneable {
private final String data;
public ImmutableObject(String data) {
this.data = data;
}
public String getData() {
return data;
}
@Override
public ImmutableObject clone() {
// 对于不可变对象,可以直接返回对象本身
return this;
}
}
10.3 问题:如何保证克隆对象和原型对象的一致性?
解决方案:在克隆过程中添加验证逻辑,确保克隆对象符合要求。
@Override
public Object clone() {
try {
Object clone = super.clone();
// 添加验证逻辑
validate(clone);
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
private void validate(Object clone) {
// 验证逻辑
// ...
if (!isValid(clone)) {
throw new IllegalStateException("克隆对象验证失败");
}
}
11. 总结
原型模式是一种强大的创建型设计模式,它允许通过克隆现有对象来创建新对象,避免了昂贵的对象创建过程。在Java中,我们可以通过实现Cloneable
接口和重写clone()
方法来实现原型模式。
原型模式的关键点在于理解浅拷贝和深拷贝的区别,以及如何根据具体需求选择合适的克隆方式。对于包含引用类型的复杂对象,通常需要实现深拷贝以避免潜在的问题。
通过本文的多个示例,希望初学者能够对原型模式有一个全面深入的理解,并能在日常编程中灵活运用这一模式。