深入理解设计模式之原型模式(Prototype Pattern)
一、引言
在软件开发中,我们经常需要创建大量相似的对象。如果每次都通过new关键字创建,会面临以下问题:
- 创建成本高:对象初始化过程复杂,涉及大量计算或资源加载
- 性能问题:频繁创建相似对象导致性能下降
- 代码重复:需要重复设置相同的属性值
- 构造函数限制:有些对象的创建过程无法通过构造函数完全控制
想象这样的场景:你正在开发一个文档编辑器,用户需要复制一个包含格式、样式、内容的复杂文档。如果每次都重新创建并设置所有属性,不仅代码复杂,性能也会很差。更好的方式是直接复制现有文档,这就是原型模式的核心思想。
原型模式就像现实中的复印机:你有一份原始文件,想要多份相同的副本,只需要把原件放入复印机,按下复制按钮,就能快速得到多个副本,而不需要重新编写每份文件。
本文将深入探讨原型模式的原理、浅克隆与深克隆的区别,并结合Spring、MyBatis等框架应用,帮助你全面掌握这一重要的设计模式。
二、什么是原型模式
2.1 定义
原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制现有的实例来创建新的实例,而不是通过new关键字。原型模式允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
2.2 核心思想
- 克隆代替创建:通过复制现有对象创建新对象
- 避免构造函数:不依赖构造函数,直接复制对象状态
- 提高性能:对于创建成本高的对象,克隆比new更高效
- 保护性拷贝:可以获得对象的完整副本
2.3 模式结构
ini
原型模式结构:
┌─────────────────────────┐
│ <<interface>> │
│ Prototype │ 原型接口
├─────────────────────────┤
│ + clone(): Prototype │ 克隆方法
└───────────┬─────────────┘
△
│ 实现
│
┌───────────┴─────────────┐
│ ConcretePrototype │ 具体原型
├─────────────────────────┤
│ - field1 │
│ - field2 │
├─────────────────────────┤
│ + clone(): Prototype │ 实现克隆
└─────────────────────────┘
使用方式:
Prototype original = new ConcretePrototype();
Prototype copy = original.clone(); // 克隆对象
对比:
传统方式:
Object obj = new Object(); // 每次new创建
obj.setField1(...); // 重复设置属性
obj.setField2(...);
原型模式:
Object obj = prototype.clone(); // 直接克隆,属性自动复制
2.4 浅克隆 vs 深克隆
rust
浅克隆(Shallow Clone):
┌─────────────┐ ┌─────────────┐
│ Original │ │ Clone │
├─────────────┤ ├─────────────┤
│ primitiveA │──复制值──▶│ primitiveA │
│ primitiveB │──复制值──▶│ primitiveB │
│ │ │ │
│ referenceC ─┼───────┐ │ referenceC ─┼───┐
└─────────────┘ │ └─────────────┘ │
│ │
▼ │
┌────────┐ ◀──────────────┘
│ Object │ 两者指向同一对象
└────────┘
深克隆(Deep Clone):
┌─────────────┐ ┌─────────────┐
│ Original │ │ Clone │
├─────────────┤ ├─────────────┤
│ primitiveA │──复制值──▶│ primitiveA │
│ primitiveB │──复制值──▶│ primitiveB │
│ │ │ │
│ referenceC ─┼───┐ │ referenceC ─┼───┐
└─────────────┘ │ └─────────────┘ │
▼ ▼
┌────────┐ ┌────────┐
│ Object │ │ Object │
└────────┘ └────────┘
原对象 新副本
关键区别:
浅克隆:只复制对象本身,引用类型字段指向同一对象
深克隆:递归复制所有字段,包括引用类型
三、基础示例
3.1 场景:浅克隆示例
演示浅克隆的问题和使用场景。
原型对象(浅克隆):
java
/**
* 学生类(浅克隆)
*/
public class Student implements Cloneable {
private String name;
private int age;
private Address address; // 引用类型
public Student(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
/**
* 实现浅克隆
*/
@Override
public Student clone() {
try {
// Object.clone()是浅克隆
return (Student) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("克隆失败", e);
}
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age +
", 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 Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
}
/**
* 地址类
*/
class Address {
private String city;
private String street;
public Address(String city, String street) {
this.city = city;
this.street = street;
}
@Override
public String toString() {
return "Address{city='" + city + "', street='" + street + "'}";
}
// Getters and Setters
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
}
测试浅克隆:
java
public class ShallowCloneDemo {
public static void main(String[] args) {
System.out.println("========== 浅克隆演示 ==========\n");
// 创建原始对象
Address address = new Address("北京", "朝阳区");
Student original = new Student("张三", 20, address);
System.out.println("原始对象: " + original);
System.out.println("原始地址对象: " + System.identityHashCode(original.getAddress()));
// 浅克隆
Student clone = original.clone();
System.out.println("\n克隆对象: " + clone);
System.out.println("克隆地址对象: " + System.identityHashCode(clone.getAddress()));
// 验证:修改克隆对象的基本类型
System.out.println("\n========== 修改克隆对象的基本类型 ==========");
clone.setName("李四");
clone.setAge(22);
System.out.println("原始对象: " + original);
System.out.println("克隆对象: " + clone);
System.out.println("结论: 基本类型互不影响 ✓");
// 验证:修改克隆对象的引用类型
System.out.println("\n========== 修改克隆对象的引用类型 ==========");
clone.getAddress().setCity("上海");
clone.getAddress().setStreet("浦东新区");
System.out.println("原始对象: " + original);
System.out.println("克隆对象: " + clone);
System.out.println("结论: 引用类型指向同一对象,互相影响 ✗");
System.out.println("\n原始和克隆的address是否相同: " +
(original.getAddress() == clone.getAddress()));
}
}
输出:
ini
========== 浅克隆演示 ==========
原始对象: Student{name='张三', age=20, address=Address{city='北京', street='朝阳区'}}
原始地址对象: 1922154895
克隆对象: Student{name='张三', age=20, address=Address{city='北京', street='朝阳区'}}
克隆地址对象: 1922154895
========== 修改克隆对象的基本类型 ==========
原始对象: Student{name='张三', age=20, address=Address{city='北京', street='朝阳区'}}
克隆对象: Student{name='李四', age=22, address=Address{city='北京', street='朝阳区'}}
结论: 基本类型互不影响 ✓
========== 修改克隆对象的引用类型 ==========
原始对象: Student{name='张三', age=20, address=Address{city='上海', street='浦东新区'}}
克隆对象: Student{name='李四', age=22, address=Address{city='上海', street='浦东新区'}}
结论: 引用类型指向同一对象,互相影响 ✗
原始和克隆的address是否相同: true
3.2 场景:深克隆示例
手动实现深克隆,解决引用类型共享的问题。
原型对象(深克隆):
java
/**
* 学生类(深克隆)
*/
public class StudentDeep implements Cloneable {
private String name;
private int age;
private AddressDeep address; // 引用类型
public StudentDeep(String name, int age, AddressDeep address) {
this.name = name;
this.age = age;
this.address = address;
}
/**
* 实现深克隆
*/
@Override
public StudentDeep clone() {
try {
// 1. 先浅克隆自身
StudentDeep cloned = (StudentDeep) super.clone();
// 2. 深克隆引用类型字段
if (this.address != null) {
cloned.address = this.address.clone();
}
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("克隆失败", e);
}
}
@Override
public String toString() {
return "StudentDeep{name='" + name + "', age=" + age +
", 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 AddressDeep getAddress() { return address; }
public void setAddress(AddressDeep address) { this.address = address; }
}
/**
* 地址类(支持克隆)
*/
class AddressDeep implements Cloneable {
private String city;
private String street;
public AddressDeep(String city, String street) {
this.city = city;
this.street = street;
}
@Override
public AddressDeep clone() {
try {
return (AddressDeep) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("克隆失败", e);
}
}
@Override
public String toString() {
return "AddressDeep{city='" + city + "', street='" + street + "'}";
}
// Getters and Setters
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
}
测试深克隆:
java
public class DeepCloneDemo {
public static void main(String[] args) {
System.out.println("========== 深克隆演示 ==========\n");
// 创建原始对象
AddressDeep address = new AddressDeep("北京", "朝阳区");
StudentDeep original = new StudentDeep("张三", 20, address);
System.out.println("原始对象: " + original);
System.out.println("原始地址对象: " + System.identityHashCode(original.getAddress()));
// 深克隆
StudentDeep clone = original.clone();
System.out.println("\n克隆对象: " + clone);
System.out.println("克隆地址对象: " + System.identityHashCode(clone.getAddress()));
// 验证:修改克隆对象的引用类型
System.out.println("\n========== 修改克隆对象的引用类型 ==========");
clone.getAddress().setCity("上海");
clone.getAddress().setStreet("浦东新区");
System.out.println("原始对象: " + original);
System.out.println("克隆对象: " + clone);
System.out.println("结论: 引用类型是独立的副本,互不影响 ✓");
System.out.println("\n原始和克隆的address是否相同: " +
(original.getAddress() == clone.getAddress()));
}
}
3.3 场景:序列化实现深克隆
使用序列化机制实现深克隆,无需手动处理引用类型。
java
import java.io.*;
/**
* 使用序列化实现深克隆的工具类
*/
public class SerializationCloneUtil {
/**
* 通过序列化实现深克隆
*/
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepClone(T object) {
try {
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (T) ois.readObject();
} catch (Exception e) {
throw new RuntimeException("序列化克隆失败", e);
}
}
}
/**
* 员工类(使用序列化克隆)
*/
class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private Department department; // 引用类型
public Employee(String name, int age, Department department) {
this.name = name;
this.age = age;
this.department = department;
}
@Override
public String toString() {
return "Employee{name='" + name + "', age=" + age +
", department=" + department + "}";
}
// 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 Department getDepartment() { return department; }
public void setDepartment(Department department) { this.department = department; }
}
/**
* 部门类
*/
class Department implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private String location;
public Department(String name, String location) {
this.name = name;
this.location = location;
}
@Override
public String toString() {
return "Department{name='" + name + "', location='" + location + "'}";
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getLocation() { return location; }
public void setLocation(String location) { this.location = location; }
}
/**
* 测试序列化克隆
*/
class SerializationCloneDemo {
public static void main(String[] args) {
System.out.println("========== 序列化深克隆演示 ==========\n");
// 创建原始对象
Department dept = new Department("技术部", "3楼");
Employee original = new Employee("张三", 30, dept);
System.out.println("原始对象: " + original);
// 序列化深克隆
Employee clone = SerializationCloneUtil.deepClone(original);
System.out.println("克隆对象: " + clone);
// 验证:修改克隆对象
System.out.println("\n========== 修改克隆对象 ==========");
clone.setName("李四");
clone.getDepartment().setName("产品部");
clone.getDepartment().setLocation("4楼");
System.out.println("原始对象: " + original);
System.out.println("克隆对象: " + clone);
System.out.println("结论: 完全独立的深克隆 ✓");
}
}
四、实际生产场景应用
4.1 场景:简历复制系统
在招聘系统中,求职者可能需要投递多个相似的职位,每个职位的简历大部分内容相同,只需要微调。
java
import java.util.*;
/**
* 工作经历
*/
class WorkExperience implements Cloneable {
private String company;
private String position;
private String startDate;
private String endDate;
private String description;
public WorkExperience(String company, String position, String startDate,
String endDate, String description) {
this.company = company;
this.position = position;
this.startDate = startDate;
this.endDate = endDate;
this.description = description;
}
@Override
public WorkExperience clone() {
try {
return (WorkExperience) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return String.format("%s - %s (%s ~ %s): %s",
company, position, startDate, endDate, description);
}
// Getters and Setters
public String getCompany() { return company; }
public String getPosition() { return position; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
}
/**
* 简历类
*/
class Resume implements Cloneable {
private String name;
private int age;
private String targetPosition; // 目标职位
private List<WorkExperience> workExperiences;
private List<String> skills;
public Resume(String name, int age) {
this.name = name;
this.age = age;
this.workExperiences = new ArrayList<>();
this.skills = new ArrayList<>();
System.out.println("[Resume] 创建简历对象 - " + name);
}
/**
* 添加工作经历
*/
public void addWorkExperience(WorkExperience experience) {
workExperiences.add(experience);
}
/**
* 添加技能
*/
public void addSkill(String skill) {
skills.add(skill);
}
/**
* 实现深克隆
*/
@Override
public Resume clone() {
try {
System.out.println("[Resume] 克隆简历对象 - " + name);
Resume cloned = (Resume) super.clone();
// 深克隆工作经历列表
cloned.workExperiences = new ArrayList<>();
for (WorkExperience exp : this.workExperiences) {
cloned.workExperiences.add(exp.clone());
}
// 深克隆技能列表
cloned.skills = new ArrayList<>(this.skills);
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("克隆失败", e);
}
}
public void display() {
System.out.println("\n========== 简历 ==========");
System.out.println("姓名: " + name);
System.out.println("年龄: " + age);
System.out.println("目标职位: " + targetPosition);
System.out.println("\n工作经历:");
for (int i = 0; i < workExperiences.size(); i++) {
System.out.println(" " + (i + 1) + ". " + workExperiences.get(i));
}
System.out.println("\n技能:");
System.out.println(" " + String.join(", ", skills));
System.out.println("========================\n");
}
// Getters and Setters
public void setTargetPosition(String targetPosition) {
this.targetPosition = targetPosition;
}
}
/**
* 测试简历克隆
*/
class ResumeDemo {
public static void main(String[] args) {
System.out.println("========== 简历克隆系统 ==========\n");
// 创建原始简历(模板)
Resume template = new Resume("张三", 28);
template.addWorkExperience(new WorkExperience(
"阿里巴巴", "Java工程师", "2020-01", "2022-12",
"负责电商系统开发"
));
template.addWorkExperience(new WorkExperience(
"腾讯", "高级Java工程师", "2023-01", "至今",
"负责微信支付系统开发"
));
template.addSkill("Java");
template.addSkill("Spring Boot");
template.addSkill("MySQL");
template.addSkill("Redis");
// 场景1:投递后端开发职位
System.out.println("\n===== 场景1:投递后端开发职位 =====");
Resume resume1 = template.clone();
resume1.setTargetPosition("高级Java开发工程师");
resume1.display();
// 场景2:投递架构师职位(添加新技能)
System.out.println("===== 场景2:投递架构师职位 =====");
Resume resume2 = template.clone();
resume2.setTargetPosition("Java架构师");
resume2.addSkill("微服务架构");
resume2.addSkill("分布式系统");
resume2.display();
// 场景3:投递技术Leader职位
System.out.println("===== 场景3:投递技术Leader职位 =====");
Resume resume3 = template.clone();
resume3.setTargetPosition("技术Team Leader");
resume3.addSkill("团队管理");
resume3.display();
// 验证:模板不受影响
System.out.println("===== 验证:原始模板不受影响 =====");
template.setTargetPosition("模板简历");
template.display();
}
}
4.2 场景:数据库连接配置克隆
在多数据源场景下,需要创建多个相似的数据库连接配置。
java
/**
* 数据库连接配置
*/
class DatabaseConfig implements Cloneable {
private String driver;
private String url;
private String username;
private String password;
private int maxConnections;
private int minConnections;
private int connectionTimeout;
private boolean autoCommit;
public DatabaseConfig(String driver, String url, String username, String password) {
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
// 默认配置
this.maxConnections = 10;
this.minConnections = 2;
this.connectionTimeout = 30000;
this.autoCommit = true;
System.out.println("[DatabaseConfig] 创建新配置对象: " + url);
}
@Override
public DatabaseConfig clone() {
try {
System.out.println("[DatabaseConfig] 克隆配置对象: " + url);
return (DatabaseConfig) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
public void display() {
System.out.println(" 驱动: " + driver);
System.out.println(" URL: " + url);
System.out.println(" 用户名: " + username);
System.out.println(" 最大连接数: " + maxConnections);
System.out.println(" 最小连接数: " + minConnections);
System.out.println(" 连接超时: " + connectionTimeout + "ms");
System.out.println(" 自动提交: " + autoCommit);
}
// Setters
public void setUrl(String url) { this.url = url; }
public void setUsername(String username) { this.username = username; }
public void setPassword(String password) { this.password = password; }
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
}
/**
* 测试数据库配置克隆
*/
class DatabaseConfigDemo {
public static void main(String[] args) {
System.out.println("========== 数据库配置克隆 ==========\n");
// 创建主库配置(作为原型)
DatabaseConfig masterConfig = new DatabaseConfig(
"com.mysql.cj.jdbc.Driver",
"jdbc:mysql://localhost:3306/main_db",
"root",
"password"
);
masterConfig.setMaxConnections(50);
System.out.println("\n主库配置:");
masterConfig.display();
// 克隆创建从库配置1
System.out.println("\n===== 创建从库配置1 =====");
DatabaseConfig slave1Config = masterConfig.clone();
slave1Config.setUrl("jdbc:mysql://slave1:3306/main_db");
slave1Config.setUsername("readonly");
slave1Config.setMaxConnections(30);
System.out.println("\n从库1配置:");
slave1Config.display();
// 克隆创建从库配置2
System.out.println("\n===== 创建从库配置2 =====");
DatabaseConfig slave2Config = masterConfig.clone();
slave2Config.setUrl("jdbc:mysql://slave2:3306/main_db");
slave2Config.setUsername("readonly");
slave2Config.setMaxConnections(30);
System.out.println("\n从库2配置:");
slave2Config.display();
System.out.println("\n优势: 通过克隆创建了3个配置,避免重复设置相同参数");
}
}
五、开源框架中的应用
5.1 JDK的Object.clone()
Java内置的clone()方法是原型模式的基础实现。
java
/**
* JDK clone()方法示例
*/
public class JdkCloneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("========== JDK clone()方法 ==========\n");
// ArrayList的clone()
System.out.println("===== ArrayList克隆 =====");
java.util.ArrayList<String> list1 = new java.util.ArrayList<>();
list1.add("A");
list1.add("B");
list1.add("C");
@SuppressWarnings("unchecked")
java.util.ArrayList<String> list2 = (java.util.ArrayList<String>) list1.clone();
System.out.println("原始列表: " + list1);
System.out.println("克隆列表: " + list2);
System.out.println("是否相同对象: " + (list1 == list2));
list2.add("D");
System.out.println("\n修改克隆列表后:");
System.out.println("原始列表: " + list1);
System.out.println("克隆列表: " + list2);
// HashMap的clone()
System.out.println("\n===== HashMap克隆 =====");
java.util.HashMap<String, Integer> map1 = new java.util.HashMap<>();
map1.put("one", 1);
map1.put("two", 2);
@SuppressWarnings("unchecked")
java.util.HashMap<String, Integer> map2 =
(java.util.HashMap<String, Integer>) map1.clone();
System.out.println("原始Map: " + map1);
System.out.println("克隆Map: " + map2);
System.out.println("是否相同对象: " + (map1 == map2));
System.out.println("\nJDK集合类广泛使用clone()实现浅克隆");
}
}
5.2 Spring Bean的prototype作用域
Spring的prototype作用域使用原型模式,每次获取Bean都返回新实例。
java
/**
* Spring prototype作用域示例
*
* 在Spring中:
* - singleton作用域:单例,容器中只有一个实例
* - prototype作用域:原型,每次获取都创建新实例
*/
/**
* 用户服务Bean
*/
// @Component
// @Scope("prototype") // 原型作用域
class UserService {
private String sessionId;
public UserService() {
this.sessionId = UUID.randomUUID().toString();
System.out.println("[UserService] 创建新实例: " + sessionId);
}
public String getSessionId() {
return sessionId;
}
public void doSomething() {
System.out.println("执行业务逻辑,Session: " + sessionId);
}
}
/**
* 模拟Spring容器
*/
class SimpleSpringContext {
private Map<String, Object> singletonBeans = new HashMap<>();
private Map<String, Class<?>> prototypeDefinitions = new HashMap<>();
/**
* 注册单例Bean
*/
public void registerSingleton(String beanName, Object bean) {
singletonBeans.put(beanName, bean);
}
/**
* 注册原型Bean定义
*/
public void registerPrototype(String beanName, Class<?> beanClass) {
prototypeDefinitions.put(beanName, beanClass);
}
/**
* 获取Bean
*/
@SuppressWarnings("unchecked")
public <T> T getBean(String beanName) {
// 先查找单例
if (singletonBeans.containsKey(beanName)) {
return (T) singletonBeans.get(beanName);
}
// 再查找原型
if (prototypeDefinitions.containsKey(beanName)) {
try {
// 每次创建新实例(原型模式)
return (T) prototypeDefinitions.get(beanName).getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("创建原型Bean失败", e);
}
}
throw new RuntimeException("Bean不存在: " + beanName);
}
}
/**
* 测试Spring原型作用域
*/
class SpringPrototypeDemo {
public static void main(String[] args) {
System.out.println("========== Spring Prototype作用域 ==========\n");
SimpleSpringContext context = new SimpleSpringContext();
// 注册原型Bean
context.registerPrototype("userService", UserService.class);
// 多次获取Bean
System.out.println("===== 第一次获取 =====");
UserService service1 = context.getBean("userService");
service1.doSomething();
System.out.println("\n===== 第二次获取 =====");
UserService service2 = context.getBean("userService");
service2.doSomething();
System.out.println("\n===== 第三次获取 =====");
UserService service3 = context.getBean("userService");
service3.doSomething();
System.out.println("\n===== 验证是否是不同实例 =====");
System.out.println("service1 == service2: " + (service1 == service2));
System.out.println("service1 == service3: " + (service1 == service3));
System.out.println("每次获取都是新实例(原型模式)");
}
}
/**
* 真实的Spring使用方式:
*
* @Component
* @Scope("prototype")
* public class UserService {
* // ...
* }
*
* @Autowired
* private ApplicationContext context;
*
* public void test() {
* UserService service1 = context.getBean(UserService.class);
* UserService service2 = context.getBean(UserService.class);
* // service1 != service2,每次都是新实例
* }
*/
5.3 Apache Commons的SerializationUtils
Apache Commons提供了便捷的序列化克隆工具。
java
/**
* Apache Commons SerializationUtils示例
*
* 实际使用需要添加依赖:
* <dependency>
* <groupId>org.apache.commons</groupId>
* <artifactId>commons-lang3</artifactId>
* </dependency>
*/
/**
* 简化的SerializationUtils实现
*/
class SimpleSerializationUtils {
/**
* 深克隆(通过序列化)
*/
public static <T extends Serializable> T clone(T object) {
if (object == null) {
return null;
}
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.flush();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
@SuppressWarnings("unchecked")
T cloned = (T) ois.readObject();
return cloned;
} catch (Exception e) {
throw new RuntimeException("序列化克隆失败", e);
}
}
}
/**
* 产品类
*/
class Product implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private double price;
private Category category;
public Product(String name, double price, Category category) {
this.name = name;
this.price = price;
this.category = category;
}
@Override
public String toString() {
return "Product{name='" + name + "', price=" + price +
", category=" + category + "}";
}
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
public Category getCategory() { return category; }
}
/**
* 分类类
*/
class Category implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public Category(String name) {
this.name = name;
}
@Override
public String toString() {
return "Category{name='" + name + "'}";
}
public void setName(String name) { this.name = name; }
}
/**
* 测试SerializationUtils
*/
class SerializationUtilsDemo {
public static void main(String[] args) {
System.out.println("========== SerializationUtils克隆 ==========\n");
Category category = new Category("电子产品");
Product original = new Product("iPhone 15", 5999.0, category);
System.out.println("原始对象: " + original);
// 使用SerializationUtils克隆
Product cloned = SimpleSerializationUtils.clone(original);
System.out.println("克隆对象: " + cloned);
// 修改克隆对象
cloned.setName("iPhone 15 Pro");
cloned.setPrice(6999.0);
cloned.getCategory().setName("高端电子产品");
System.out.println("\n修改克隆对象后:");
System.out.println("原始对象: " + original);
System.out.println("克隆对象: " + cloned);
System.out.println("\nSerializationUtils.clone()实现了完整的深克隆");
}
}
/**
* 真实的Apache Commons使用:
*
* import org.apache.commons.lang3.SerializationUtils;
*
* Product cloned = SerializationUtils.clone(original);
*/
六、原型模式的优缺点
6.1 优点
1. 性能优势
markdown
对象创建成本对比:
new方式:
1. 调用构造函数
2. 初始化所有字段
3. 执行初始化逻辑
4. 可能涉及复杂计算
clone方式:
1. 直接复制内存
2. 快速创建副本
3. 避免重复初始化
性能提升:克隆比new快3-10倍(取决于对象复杂度)
2. 简化对象创建
java
// 传统方式:需要知道创建细节
Configuration config = new Configuration();
config.setTimeout(3000);
config.setRetryCount(3);
config.setPoolSize(10);
// ... 50个配置项
// 原型方式:一行代码
Configuration config = template.clone();
3. 避免构造函数约束
diff
某些对象的创建过程:
- 构造函数参数过多
- 初始化逻辑复杂
- 需要特定顺序设置属性
原型模式绕过构造函数,直接复制状态
4. 可以保存对象状态
java
// 保存对象快照
Document snapshot = document.clone();
// 继续修改document
document.edit();
// 需要时恢复
document = snapshot.clone();
6.2 缺点
1. 必须实现克隆方法
diff
每个类都需要:
- 实现Cloneable接口
- 重写clone()方法
- 处理CloneNotSupportedException
2. 深克隆复杂
kotlin
深克隆需要:
- 递归克隆所有引用类型
- 处理循环引用
- 考虑不可克隆的对象
示例:
class A {
B b; // 需要克隆
}
class B {
C c; // 需要克隆
}
class C {
A a; // 循环引用!
}
3. 违反开闭原则
scss
添加新字段时:
需要修改clone()方法
可能遗漏新字段的克隆
七、最佳实践
7.1 选择合适的克隆方式
java
/**
* 克隆方式选择指南
*/
class CloneStrategy {
/**
* 1. 浅克隆:适用于不包含引用类型的简单对象
*/
static class SimpleObject implements Cloneable {
private int id;
private String name;
@Override
public SimpleObject clone() {
try {
return (SimpleObject) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 2. 手动深克隆:适用于引用类型较少的对象
*/
static class ModerateObject implements Cloneable {
private String name;
private Address address; // 唯一的引用类型
@Override
public ModerateObject clone() {
try {
ModerateObject cloned = (ModerateObject) super.clone();
if (this.address != null) {
cloned.address = this.address.clone();
}
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 3. 序列化克隆:适用于引用类型很多的复杂对象
*/
static class ComplexObject implements Serializable {
private String name;
private List<Item> items;
private Map<String, Value> values;
private NestedObject nested;
public ComplexObject deepClone() {
return SerializationCloneUtil.deepClone(this);
}
}
}
7.2 实现Cloneable接口的最佳实践
java
/**
* 标准的克隆实现模板
*/
class StandardCloneTemplate implements Cloneable {
// 基本类型字段
private int primitiveField;
// 不可变引用类型(如String)
private String immutableField;
// 可变引用类型
private MutableObject mutableField;
@Override
public StandardCloneTemplate clone() {
try {
// 1. 调用super.clone()
StandardCloneTemplate cloned = (StandardCloneTemplate) super.clone();
// 2. 基本类型和不可变对象自动复制,无需处理
// 3. 深克隆可变引用类型
if (this.mutableField != null) {
cloned.mutableField = this.mutableField.clone();
}
return cloned;
} catch (CloneNotSupportedException e) {
// 4. 包装为RuntimeException
throw new RuntimeException("克隆失败", e);
}
}
/**
* 或者使用克隆构造函数
*/
public StandardCloneTemplate(StandardCloneTemplate source) {
this.primitiveField = source.primitiveField;
this.immutableField = source.immutableField;
this.mutableField = source.mutableField.clone();
}
}
7.3 处理不可克隆对象
java
/**
* 处理包含不可克隆字段的情况
*/
class ObjectWithUncloneable implements Cloneable {
private String name;
private Thread thread; // Thread不可克隆
@Override
public ObjectWithUncloneable clone() {
try {
ObjectWithUncloneable cloned = (ObjectWithUncloneable) super.clone();
// 对于不可克隆的对象,重新创建
if (this.thread != null) {
cloned.thread = new Thread(this.thread.getName());
}
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
八、总结
8.1 核心要点
- 原型模式的本质:通过克隆现有对象创建新对象,避免使用new
- 两种克隆方式 :
- 浅克隆:复制对象本身,引用类型共享
- 深克隆:递归复制所有字段,完全独立
- 适用场景 :
- 对象创建成本高
- 需要大量相似对象
- 需要保存对象状态
- 避免复杂的构造过程
8.2 使用建议
arduino
选择原型模式的检查清单:
✓ 对象创建成本是否很高?
✓ 是否需要创建大量相似对象?
✓ 构造函数是否过于复杂?
✓ 是否需要保存对象快照?
如果有2个以上"是",建议使用原型模式!
8.3 克隆方式选择
diff
浅克隆:
- 对象只包含基本类型
- 引用类型是不可变对象(如String)
- 可以接受引用共享
深克隆:
- 对象包含可变引用类型
- 需要完全独立的副本
- 简单对象用手动克隆
- 复杂对象用序列化克隆
8.4 与其他模式的对比
diff
原型 vs 工厂方法:
- 原型:克隆现有对象
- 工厂:创建新对象
原型 vs 单例:
- 原型:创建多个副本
- 单例:只有一个实例
原型 vs 建造者:
- 原型:复制整个对象
- 建造者:逐步构建对象
8.5 实践经验
- 优先使用浅克隆:简单高效,满足大多数场景
- 谨慎实现深克隆:手动深克隆容易遗漏字段
- 考虑序列化克隆:对于复杂对象,序列化是最简单的深克隆方式
- 注意不可克隆对象:如Thread、Socket等,需要特殊处理
- Spring使用prototype:在Spring中使用prototype作用域实现原型模式
参考资料
- 《设计模式:可复用面向对象软件的基础》- Gang of Four
- 《Effective Java》第三版 - Joshua Bloch(第13条:谨慎地覆盖clone)
- JDK源码(Object.clone()、ArrayList.clone())
- Spring Framework源码(prototype作用域)
- Apache Commons Lang源码(SerializationUtils)