原型模式(Prototype)
原型模式是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是通过 new 关键字实例化。原型模式适用于创建成本较高的对象,或者需要保持对象状态的场景。
模式定义
原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
核心要点
- 克隆创建:通过复制现有对象创建新对象
- 避免重复初始化:复用已有对象的状态
- 动态创建:可以在运行时动态创建对象
- 隐藏创建细节:客户端不需要知道对象的具体类型
问题场景
假设我们需要创建大量相似的图形对象,每个图形都有复杂的初始化过程:
// 问题代码:每次都重新创建和初始化
public class Shape {
private String type;
private String color;
private int x, y;
private List<String> attributes; // 复杂的属性列表
public Shape(String type) {
this.type = type;
this.attributes = new ArrayList<>();
// 模拟耗时的初始化
loadAttributesFromDatabase();
}
private void loadAttributesFromDatabase() {
// 模拟数据库查询
try {
Thread.sleep(100); // 耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
attributes.add("attr1");
attributes.add("attr2");
}
// getters and setters
}
// 使用
public class Client {
public static void main(String[] args) {
// 每次创建都需要重新初始化
Shape shape1 = new Shape("circle"); // 耗时
Shape shape2 = new Shape("circle"); // 重复耗时
Shape shape3 = new Shape("circle"); // 重复耗时
}
}
存在的问题:
- 每次创建对象都需要重复初始化
- 初始化过程耗时,影响性能
- 无法快速创建相似对象
- 对象创建过程对客户端不透明
解决方案
使用原型模式,通过克隆现有对象来创建新对象:
// 原型接口
public interface Prototype<T> {
T clone();
}
// 具体原型
public class Shape implements Prototype<Shape> {
private String type;
private String color;
private int x, y;
private List<String> attributes;
public Shape(String type) {
this.type = type;
this.attributes = new ArrayList<>();
loadAttributesFromDatabase();
}
// 私有构造方法,用于克隆
private Shape(Shape source) {
this.type = source.type;
this.color = source.color;
this.x = source.x;
this.y = source.y;
// 深拷贝属性列表
this.attributes = new ArrayList<>(source.attributes);
}
private void loadAttributesFromDatabase() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
attributes.add("attr1");
attributes.add("attr2");
}
@Override
public Shape clone() {
return new Shape(this);
}
// getters and setters
public void setColor(String color) { this.color = color; }
public void setPosition(int x, int y) { this.x = x; this.y = y; }
@Override
public String toString() {
return "Shape{type='" + type + "', color='" + color +
"', x=" + x + ", y=" + y + ", attributes=" + attributes + "}";
}
}
// 原型注册表
public class ShapeRegistry {
private Map<String, Shape> prototypes = new HashMap<>();
public void register(String key, Shape prototype) {
prototypes.put(key, prototype);
}
public Shape get(String key) {
return prototypes.get(key).clone();
}
}
// 使用示例
public class Client {
public static void main(String[] args) {
// 创建原型注册表
ShapeRegistry registry = new ShapeRegistry();
// 初始化原型(只执行一次耗时操作)
Shape circlePrototype = new Shape("circle");
circlePrototype.setColor("red");
registry.register("circle", circlePrototype);
// 通过克隆快速创建对象
Shape shape1 = registry.get("circle");
shape1.setPosition(10, 20);
Shape shape2 = registry.get("circle");
shape2.setPosition(30, 40);
System.out.println(shape1);
System.out.println(shape2);
}
}
输出:
Shape{type='circle', color='red', x=10, y=20, attributes=[attr1, attr2]}
Shape{type='circle', color='red', x=30, y=40, attributes=[attr1, attr2]}
模式结构
组成部分:
- Prototype(原型接口):声明克隆方法的接口
- ConcretePrototype(具体原型):实现克隆方法
- Client(客户端):通过调用原型对象的克隆方法来创建新对象
浅拷贝 vs 深拷贝
浅拷贝
只复制对象本身和基本类型字段,引用类型字段仍然指向原对象:
// 浅拷贝示例
public class ShallowCopyExample implements Cloneable {
private String name;
private List<String> items; // 引用类型
@Override
public ShallowCopyExample clone() {
try {
return (ShallowCopyExample) super.clone(); // 浅拷贝
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
// 问题:修改克隆对象的 items 会影响原对象
ShallowCopyExample original = new ShallowCopyExample();
original.items.add("item1");
ShallowCopyExample cloned = original.clone();
cloned.items.add("item2"); // 原对象的 items 也会被修改!
深拷贝
递归复制所有引用类型字段,创建完全独立的对象:
// 深拷贝示例
public class DeepCopyExample implements Cloneable {
private String name;
private List<String> items;
private NestedObject nested; // 嵌套对象
@Override
public DeepCopyExample clone() {
try {
DeepCopyExample cloned = (DeepCopyExample) super.clone();
// 深拷贝引用类型
cloned.items = new ArrayList<>(this.items);
cloned.nested = this.nested.clone(); // 嵌套对象也需要实现克隆
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
// 嵌套对象也需要实现克隆
public class NestedObject implements Cloneable {
private String value;
@Override
public NestedObject clone() {
try {
return (NestedObject) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
深拷贝的其他实现方式
1. 序列化方式
import java.io.*;
public class SerializationUtils {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepCopy(T object) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.flush();
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
// 使用
public class Person implements Serializable {
private String name;
private Address address; // 也需要实现 Serializable
public Person deepCopy() {
return SerializationUtils.deepCopy(this);
}
}
2. JSON 序列化方式
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonDeepCopy {
private static final ObjectMapper mapper = new ObjectMapper();
public static <T> T deepCopy(T object, Class<T> clazz) {
try {
String json = mapper.writeValueAsString(object);
return mapper.readValue(json, clazz);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
实现方式
1. 实现 Cloneable 接口
public class Student implements Cloneable {
private String name;
private int age;
private List<String> courses;
public Student(String name, int age) {
this.name = name;
this.age = age;
this.courses = new ArrayList<>();
}
@Override
public Student clone() {
try {
Student cloned = (Student) super.clone();
cloned.courses = new ArrayList<>(this.courses); // 深拷贝
return cloned;
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 不会发生,因为实现了 Cloneable
}
}
// getters, setters
public void addCourse(String course) { courses.add(course); }
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + ", courses=" + courses + "}";
}
}
// 使用
Student original = new Student("张三", 20);
original.addCourse("数学");
Student cloned = original.clone();
cloned.addCourse("英语"); // 不影响原对象
System.out.println(original); // courses=[数学]
System.out.println(cloned); // courses=[数学, 英语]
2. 自定义克隆方法
public interface Prototype<T> {
T copy();
}
public class Document implements Prototype<Document> {
private String title;
private String content;
private List<String> tags;
private Date createdAt;
public Document() {
this.tags = new ArrayList<>();
this.createdAt = new Date();
}
@Override
public Document copy() {
Document doc = new Document();
doc.title = this.title;
doc.content = this.content;
doc.tags = new ArrayList<>(this.tags); // 深拷贝
doc.createdAt = new Date(this.createdAt.getTime()); // 深拷贝
return doc;
}
}
3. 原型注册表
public class PrototypeRegistry {
private Map<String, Prototype<?>> prototypes = new HashMap<>();
public void register(String key, Prototype<?> prototype) {
prototypes.put(key, prototype);
}
@SuppressWarnings("unchecked")
public <T> T get(String key) {
Prototype<?> prototype = prototypes.get(key);
if (prototype == null) {
throw new IllegalArgumentException("未找到原型: " + key);
}
return (T) prototype.copy();
}
public void unregister(String key) {
prototypes.remove(key);
}
}
// 使用
PrototypeRegistry registry = new PrototypeRegistry();
// 注册原型
Document template = new Document();
template.setTitle("模板标题");
template.addTag("模板");
registry.register("document-template", template);
// 获取克隆
Document doc1 = registry.get("document-template");
Document doc2 = registry.get("document-template");
实际应用案例
1. Java Object.clone()
Java 的 Object 类提供了 clone() 方法:
// 数组克隆
int[] original = {1, 2, 3, 4, 5};
int[] cloned = original.clone(); // 浅拷贝
// ArrayList 克隆
ArrayList<String> list = new ArrayList<>();
list.add("a");
list.add("b");
@SuppressWarnings("unchecked")
ArrayList<String> clonedList = (ArrayList<String>) list.clone();
2. BeanUtils.copyProperties()
Spring 的 BeanUtils 用于属性复制:
public class UserDTO {
private String name;
private String email;
// getters, setters
}
public class UserEntity {
private String name;
private String email;
// getters, setters
}
// 属性复制
UserEntity entity = new UserEntity();
entity.setName("张三");
entity.setEmail("[email protected]");
UserDTO dto = new UserDTO();
BeanUtils.copyProperties(entity, dto); // 属性复制
3. Java 流式 API
Java Stream 的操作返回新的集合:
List<Integer> original = Arrays.asList(1, 2, 3, 4, 5);
// 创建新列表,不影响原列表
List<Integer> filtered = original.stream()
.filter(x -> x > 2)
.collect(Collectors.toList());
// 创建新列表,不影响原列表
List<Integer> mapped = original.stream()
.map(x -> x * 2)
.collect(Collectors.toList());
4. 不可变对象的"修改"
不可变对象通过创建新对象来实现"修改":
// String 是不可变对象
String s1 = "Hello";
String s2 = s1.concat(" World"); // 创建新对象,s1 不变
// LocalDate 是不可变对象
LocalDate date = LocalDate.of(2024, 1, 1);
LocalDate nextWeek = date.plusWeeks(1); // 创建新对象,date 不变
原型模式 vs 工厂模式
| 特性 | 原型模式 | 工厂模式 |
|---|---|---|
| 创建方式 | 克隆现有对象 | 实例化新对象 |
| 对象状态 | 复制现有状态 | 初始状态 |
| 性能 | 克隆较快 | 可能较慢 |
| 适用场景 | 创建相似对象 | 创建不同类型对象 |
优缺点分析
优点
- 性能优化:克隆比创建新对象更快
- 简化创建:不需要知道对象的具体类型
- 动态添加:可以在运行时动态添加原型
- 状态复用:可以复用已有对象的状态
缺点
- 克隆复杂:深拷贝实现较复杂
- 循环引用:处理循环引用较困难
- 构造方法绕过:克隆不调用构造方法
使用建议
何时使用原型模式
- 创建对象成本较高
- 需要创建大量相似对象
- 对象初始化过程复杂
- 需要保持对象的状态
何时避免使用原型模式
- 对象创建简单
- 对象之间差异很大
- 不需要复制对象状态
最佳实践
- 明确拷贝类型:根据需求选择浅拷贝或深拷贝
- 实现 Cloneable:使用 Java 的克隆机制
- 处理循环引用:注意处理对象间的循环引用
- 考虑不可变性:不可变对象不需要深拷贝
小结
原型模式是通过克隆创建对象的方式:
| 拷贝类型 | 特点 | 适用场景 |
|---|---|---|
| 浅拷贝 | 复制基本类型,引用共享 | 对象简单,无引用类型 |
| 深拷贝 | 完全独立的新对象 | 对象复杂,有引用类型 |
| 序列化 | 自动深拷贝 | 对象实现了 Serializable |
练习
- 实现一个支持深拷贝的 Employee 类,包含姓名、部门和下属列表
- 使用原型模式实现一个简历模板系统
- 实现一个图形编辑器的复制粘贴功能
- 比较浅拷贝和深拷贝的性能差异