跳到主要内容

原型模式(Prototype)

原型模式是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是通过 new 关键字实例化。原型模式适用于创建成本较高的对象,或者需要保持对象状态的场景。

模式定义

原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

核心要点

  1. 克隆创建:通过复制现有对象创建新对象
  2. 避免重复初始化:复用已有对象的状态
  3. 动态创建:可以在运行时动态创建对象
  4. 隐藏创建细节:客户端不需要知道对象的具体类型

问题场景

假设我们需要创建大量相似的图形对象,每个图形都有复杂的初始化过程:

// 问题代码:每次都重新创建和初始化
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 工厂模式

特性原型模式工厂模式
创建方式克隆现有对象实例化新对象
对象状态复制现有状态初始状态
性能克隆较快可能较慢
适用场景创建相似对象创建不同类型对象

优缺点分析

优点

  1. 性能优化:克隆比创建新对象更快
  2. 简化创建:不需要知道对象的具体类型
  3. 动态添加:可以在运行时动态添加原型
  4. 状态复用:可以复用已有对象的状态

缺点

  1. 克隆复杂:深拷贝实现较复杂
  2. 循环引用:处理循环引用较困难
  3. 构造方法绕过:克隆不调用构造方法

使用建议

何时使用原型模式

  • 创建对象成本较高
  • 需要创建大量相似对象
  • 对象初始化过程复杂
  • 需要保持对象的状态

何时避免使用原型模式

  • 对象创建简单
  • 对象之间差异很大
  • 不需要复制对象状态

最佳实践

  1. 明确拷贝类型:根据需求选择浅拷贝或深拷贝
  2. 实现 Cloneable:使用 Java 的克隆机制
  3. 处理循环引用:注意处理对象间的循环引用
  4. 考虑不可变性:不可变对象不需要深拷贝

小结

原型模式是通过克隆创建对象的方式:

拷贝类型特点适用场景
浅拷贝复制基本类型,引用共享对象简单,无引用类型
深拷贝完全独立的新对象对象复杂,有引用类型
序列化自动深拷贝对象实现了 Serializable

练习

  1. 实现一个支持深拷贝的 Employee 类,包含姓名、部门和下属列表
  2. 使用原型模式实现一个简历模板系统
  3. 实现一个图形编辑器的复制粘贴功能
  4. 比较浅拷贝和深拷贝的性能差异

参考资源