原型模式:设计与实践
一、什么是原型模式
1. 基本定义
原型模式(Prototype Pattern)是一种创建型设计模式,由《设计模式:可复用面向对象软件的基础》(GOF著作)定义为:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
该模式通过复制现有对象(原型)来创建新对象,而无需依赖类的构造函数,从而简化对象创建过程,提高创建效率,尤其适合创建复杂或初始化成本高的对象。
2. 核心思想
原型模式的核心在于通过"复制"而非"重新创建"的方式生成新对象。它将对象的创建过程从"根据类构造"转变为"根据现有实例复制",允许在运行时动态获取对象的状态并以此为基础生成新对象,实现了对象创建与类实现的解耦。
二、原型模式的特点
1. 基于实例复制创建新对象
新对象的创建不是通过调用类的构造函数,而是通过复制现有实例(原型)的状态来实现,避免了重复的初始化过程。
2. 动态创建对象
可以在运行时根据需要复制任意原型对象,无需提前知道具体的类信息,提高了系统的灵活性和动态性。
3. 简化对象创建流程
对于构造过程复杂、初始化参数多或依赖外部资源的对象,复制现有实例比重新创建更高效,代码也更简洁。
4. 支持深拷贝与浅拷贝
根据对象的组成(基本类型与引用类型),可以选择浅拷贝(仅复制基本类型)或深拷贝(复制引用对象),灵活应对不同场景。
5. 隐藏对象创建细节
客户端只需调用原型的复制方法即可创建新对象,无需了解对象的具体创建细节和内部结构。
| 特点 | 说明 |
|---|---|
| 实例复制创建 | 通过复制现有实例生成新对象,而非使用构造函数 |
| 动态创建 | 运行时可动态复制任意原型,无需提前知道类信息 |
| 简化创建流程 | 避免复杂对象的重复初始化,提高创建效率 |
| 支持深浅拷贝 | 可根据需求选择拷贝深度,灵活处理引用类型 |
| 隐藏创建细节 | 客户端无需了解对象内部结构,只需调用复制方法 |
三、原型模式的标准代码实现
1. 模式结构
原型模式包含两个核心角色:
- 抽象原型(Prototype):声明复制方法的接口或抽象类
- 具体原型(ConcretePrototype):实现抽象原型的复制方法,完成具体的复制逻辑
2. 代码实现示例
2.1 抽象原型接口
/**
* 抽象原型接口
* 声明复制方法
*/
public interface Prototype<T> {
/**
* 复制方法
* @return 复制得到的新对象
*/
T clone();
}
2.2 具体原型实现(浅拷贝)
import java.util.Date;
/**
* 具体原型类(浅拷贝实现)
* 包含基本类型和引用类型字段
*/
public class ConcretePrototypeShallow implements Prototype<ConcretePrototypeShallow> {
private String id;
private String name;
private int age;
private Date createTime; // 引用类型
// 构造函数
public ConcretePrototypeShallow(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
this.createTime = new Date();
System.out.println("ConcretePrototypeShallow构造函数执行,初始化资源");
}
/**
* 浅拷贝实现
* 基本类型字段直接复制,引用类型字段复制引用地址
*/
@Override
public ConcretePrototypeShallow clone() {
try {
// 调用Object的clone()方法实现浅拷贝
return (ConcretePrototypeShallow) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("浅拷贝失败", e);
}
}
// getter和setter方法
public String getId() { return id; }
public void setId(String id) { this.id = id; }
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 Date getCreateTime() { return createTime; }
public void setCreateTime(Date createTime) { this.createTime = createTime; }
}
2.3 具体原型实现(深拷贝)
import java.util.Date;
/**
* 具体原型类(深拷贝实现)
* 对引用类型字段也进行复制
*/
public class ConcretePrototypeDeep implements Prototype<ConcretePrototypeDeep> {
private String id;
private String name;
private int age;
private Date createTime; // 引用类型
// 构造函数
public ConcretePrototypeDeep(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
this.createTime = new Date();
System.out.println("ConcretePrototypeDeep构造函数执行,初始化资源");
}
/**
* 深拷贝实现
* 不仅复制基本类型,也复制引用类型对象
*/
@Override
public ConcretePrototypeDeep clone() {
try {
// 先进行浅拷贝
ConcretePrototypeDeep clone = (ConcretePrototypeDeep) super.clone();
// 对引用类型字段进行深拷贝
clone.createTime = (Date) this.createTime.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("深拷贝失败", e);
}
}
// getter和setter方法
public String getId() { return id; }
public void setId(String id) { this.id = id; }
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 Date getCreateTime() { return createTime; }
public void setCreateTime(Date createTime) { this.createTime = createTime; }
}
2.4 客户端使用
import java.util.Date;
/**
* 客户端使用示例
* 演示浅拷贝和深拷贝的区别
*/
public class PrototypeClient {
public static void main(String[] args) throws InterruptedException {
// 测试浅拷贝
System.out.println("=== 测试浅拷贝 ===");
ConcretePrototypeShallow shallowPrototype = new ConcretePrototypeShallow("1", "浅拷贝原型", 20);
ConcretePrototypeShallow shallowClone = shallowPrototype.clone();
// 输出原型和克隆对象的信息
System.out.println("原型对象: " + shallowPrototype);
System.out.println("克隆对象: " + shallowClone);
System.out.println("原型与克隆是否为同一对象: " + (shallowPrototype == shallowClone));
System.out.println("原型createTime: " + shallowPrototype.getCreateTime());
System.out.println("克隆createTime: " + shallowClone.getCreateTime());
System.out.println("createTime引用是否相同: " + (shallowPrototype.getCreateTime() == shallowClone.getCreateTime()));
// 修改原型的引用类型字段,观察克隆对象的变化
Thread.sleep(1000);
shallowPrototype.getCreateTime().setTime(System.currentTimeMillis());
System.out.println("修改原型createTime后:");
System.out.println("原型createTime: " + shallowPrototype.getCreateTime());
System.out.println("克隆createTime: " + shallowClone.getCreateTime());
// 测试深拷贝
System.out.println("\n=== 测试深拷贝 ===");
ConcretePrototypeDeep deepPrototype = new ConcretePrototypeDeep("2", "深拷贝原型", 30);
ConcretePrototypeDeep deepClone = deepPrototype.clone();
System.out.println("原型对象: " + deepPrototype);
System.out.println("克隆对象: " + deepClone);
System.out.println("原型与克隆是否为同一对象: " + (deepPrototype == deepClone));
System.out.println("原型createTime: " + deepPrototype.getCreateTime());
System.out.println("克隆createTime: " + deepClone.getCreateTime());
System.out.println("createTime引用是否相同: " + (deepPrototype.getCreateTime() == deepClone.getCreateTime()));
// 修改原型的引用类型字段,观察克隆对象的变化
Thread.sleep(1000);
deepPrototype.getCreateTime().setTime(System.currentTimeMillis());
System.out.println("修改原型createTime后:");
System.out.println("原型createTime: " + deepPrototype.getCreateTime());
System.out.println("克隆createTime: " + deepClone.getCreateTime());
}
}
3. 代码实现特点总结
| 实现类型 | 核心逻辑 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 浅拷贝 | 调用super.clone(),直接复制字段值 | 实现简单,效率高 | 引用类型字段共享内存,可能导致意外修改 | 对象仅包含基本类型字段,或引用类型不可变 |
| 深拷贝 | 在浅拷贝基础上,对引用类型字段也进行复制 | 完全独立的新对象,安全性高 | 实现复杂,效率较低,可能存在循环引用问题 | 对象包含可变的引用类型字段,需要完全独立的副本 |
四、支付框架设计中原型模式的运用
以批量退款请求生成为例,说明原型模式在支付系统中的具体实现:
1. 场景分析
在支付系统中,商户经常需要发起批量退款操作。一笔主订单下可能包含多笔子订单,每笔子订单的退款请求都需要包含:
- 共性信息:主订单号、商户ID、支付渠道、币种、回调地址等
- 差异信息:子订单号、退款金额、退款原因、退款单号等
如果为每笔子订单重新创建完整的退款请求对象,会重复初始化大量共性信息,降低系统效率。使用原型模式可以通过复制原型对象快速生成新的退款请求,只修改差异信息。
2. 设计实现
2.1 退款请求原型类
import java.math.BigDecimal;
import java.util.Date;
import java.util.UUID;
/**
* 退款请求原型类
* 实现Prototype接口,支持复制操作
*/
public class RefundRequest implements Prototype<RefundRequest> {
// 共性字段(所有子订单共享)
private String originalOrderId; // 原订单号
private String merchantId; // 商户ID
private String channelCode; // 支付渠道编码
private String currency; // 币种
private String notifyUrl; // 回调地址
private String version; // 接口版本
// 差异字段(每笔子订单不同)
private String refundId; // 退款单号
private String subOrderId; // 子订单号
private BigDecimal refundAmount; // 退款金额
private String reason; // 退款原因
private Date applyTime; // 申请时间
/**
* 构造函数
* 初始化共性字段,创建原型对象
*/
public RefundRequest(String originalOrderId, String merchantId, String channelCode) {
this.originalOrderId = originalOrderId;
this.merchantId = merchantId;
this.channelCode = channelCode;
this.currency = "CNY"; // 默认人民币
this.notifyUrl = buildNotifyUrl(merchantId); // 构建默认回调地址
this.version = "1.0"; // 默认接口版本
System.out.println("创建退款请求原型,初始化共性信息: " + originalOrderId);
}
/**
* 深拷贝实现
* 基本类型直接复制,引用类型需要单独处理
*/
@Override
public RefundRequest clone() {
try {
// 先进行浅拷贝
RefundRequest clone = (RefundRequest) super.clone();
// 对引用类型进行深拷贝
clone.applyTime = new Date(this.applyTime != null ? this.applyTime.getTime() : System.currentTimeMillis());
// 生成新的退款单号(每笔退款必须唯一)
clone.refundId = generateRefundId();
return clone;
} catch (CloneNotSupportedException e) {
throw new RuntimeException("退款请求复制失败", e);
}
}
/**
* 创建子订单退款请求
* 封装克隆+设置差异字段的完整流程
*/
public RefundRequest createSubRefundRequest(String subOrderId, BigDecimal refundAmount, String reason) {
RefundRequest subRefund = this.clone();
// 设置差异字段
subRefund.subOrderId = subOrderId;
subRefund.refundAmount = refundAmount;
subRefund.reason = reason;
subRefund.applyTime = new Date();
return subRefund;
}
/**
* 生成唯一退款单号
*/
private String generateRefundId() {
return "REFUND_" + originalOrderId + "_" +
UUID.randomUUID().toString().replace("-", "").substring(0, 8);
}
/**
* 构建默认回调地址
*/
private String buildNotifyUrl(String merchantId) {
return "https://api.payment.com/merchant/" + merchantId + "/refund/notify";
}
// Getter和Setter方法
public String getOriginalOrderId() { return originalOrderId; }
public String getMerchantId() { return merchantId; }
public String getRefundId() { return refundId; }
public String getSubOrderId() { return subOrderId; }
public BigDecimal getRefundAmount() { return refundAmount; }
@Override
public String toString() {
return "RefundRequest{" +
"refundId='" + refundId + '\'' +
", originalOrderId='" + originalOrderId + '\'' +
", subOrderId='" + subOrderId + '\'' +
", amount=" + refundAmount +
", merchantId='" + merchantId + '\'' +
", channelCode='" + channelCode + '\'' +
'}';
}
}
2.2 批量退款服务类
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* 批量退款服务
* 使用原型模式处理批量退款请求
*/
public class BatchRefundService {
/**
* 处理批量退款
* @param originalOrderId 原订单号
* @param merchantId 商户ID
* @param channelCode 支付渠道
* @param subRefundDetails 子订单退款详情列表
* @return 生成的所有退款请求
*/
public List<RefundRequest> processBatchRefund(String originalOrderId, String merchantId,
String channelCode, List<SubRefundDetail> subRefundDetails) {
// 1. 创建原型对象(初始化共性字段)
RefundRequest prototype = new RefundRequest(originalOrderId, merchantId, channelCode);
// 2. 复制原型,生成各子订单的退款请求
List<RefundRequest> refundRequests = new ArrayList<>();
for (SubRefundDetail detail : subRefundDetails) {
// 克隆原型并设置差异字段
RefundRequest refundRequest = prototype.createSubRefundRequest(
detail.getSubOrderId(),
detail.getRefundAmount(),
detail.getReason()
);
refundRequests.add(refundRequest);
}
return refundRequests;
}
/**
* 提交退款请求
*/
public void submitRefundRequests(List<RefundRequest> refundRequests) {
for (RefundRequest request : refundRequests) {
System.out.println("提交退款请求: " + request);
// 实际实现中会调用支付渠道接口提交退款
// refundClient.submit(request);
}
}
}
/**
* 子订单退款详情
* 包含每笔子订单的差异信息
*/
class SubRefundDetail {
private String subOrderId;
private BigDecimal refundAmount;
private String reason;
public SubRefundDetail(String subOrderId, BigDecimal refundAmount, String reason) {
this.subOrderId = subOrderId;
this.refundAmount = refundAmount;
this.reason = reason;
}
// Getter方法
public String getSubOrderId() { return subOrderId; }
public BigDecimal getRefundAmount() { return refundAmount; }
public String getReason() { return reason; }
}
2.3 客户端使用示例
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
/**
* 客户端示例
* 演示批量退款请求的生成过程
*/
public class RefundClient {
public static void main(String[] args) {
// 创建批量退款服务
BatchRefundService refundService = new BatchRefundService();
// 准备子订单退款详情(差异信息)
List<SubRefundDetail> details = Arrays.asList(
new SubRefundDetail("SUB001", new BigDecimal("100.00"), "商品质量问题"),
new SubRefundDetail("SUB002", new BigDecimal("50.00"), "用户取消订单"),
new SubRefundDetail("SUB003", new BigDecimal("75.50"), "地址错误")
);
// 处理批量退款
List<RefundRequest> refundRequests = refundService.processBatchRefund(
"ORDER123456789", // 原订单号
"MERCHANT001", // 商户ID
"ALIPAY", // 支付渠道
details
);
// 提交退款请求
refundService.submitRefundRequests(refundRequests);
}
}
3. 模式价值体现
- 提高效率:避免重复初始化共性字段(如商户ID、渠道编码),尤其在批量处理大量子订单时效果显著
- 保证一致性:所有子订单退款请求的共性信息完全一致,避免因手动设置导致的配置错误
- 简化逻辑:通过
createSubRefundRequest方法封装克隆+差异设置的完整流程,客户端无需关心复制细节 - 灵活性高:支持动态生成新的退款请求,可根据业务需求灵活调整批量大小和退款参数
五、开源框架中原型模式的运用
以Spring框架中的BeanDefinition为例,说明原型模式在框架级别的应用:
1. 核心实现分析
Spring框架中,BeanDefinition用于描述Bean的定义信息,包含Bean的类名、属性、构造函数参数、依赖关系等元数据。Spring容器在初始化过程中,需要根据BeanDefinition创建多个Bean实例,广泛使用了原型模式。
1.1 BeanDefinition接口与实现
// 简化的BeanDefinition接口
public interface BeanDefinition {
// 获取Bean的类名
String getBeanClassName();
// 设置Bean的类名
void setBeanClassName(String beanClassName);
// 获取Bean的作用域(singleton/prototype)
String getScope();
// 设置Bean的作用域
void setScope(String scope);
// 其他方法...
}
// 具体实现类
public class GenericBeanDefinition implements BeanDefinition, Cloneable {
private String beanClassName;
private String scope;
private boolean lazyInit;
// 其他属性...
@Override
public String getBeanClassName() {
return this.beanClassName;
}
@Override
public void setBeanClassName(String beanClassName) {
this.beanClassName = beanClassName;
}
@Override
public String getScope() {
return this.scope;
}
@Override
public void setScope(String scope) {
this.scope = scope;
}
/**
* 克隆方法实现
* 支持BeanDefinition的复制
*/
@Override
public GenericBeanDefinition clone() {
try {
return (GenericBeanDefinition) super.clone();
} catch (CloneNotSupportedException ex) {
throw new IllegalStateException("Shouldn't happen because Cloneable is implemented", ex);
}
}
// 其他方法实现...
}
1.2 原型模式的应用场景
Spring在以下场景中使用BeanDefinition的克隆功能:
- 处理Bean的继承关系:子
BeanDefinition可以通过克隆父BeanDefinition并修改差异属性来创建 - 处理原型作用域(prototype)的Bean:每次请求原型Bean时,容器会克隆
BeanDefinition并创建新实例 - 处理Bean的动态注册:在运行时动态注册Bean时,通过克隆现有
BeanDefinition快速创建新的定义
1.3 克隆方法的使用
Spring内部在BeanDefinitionReader、BeanFactory等组件中广泛使用BeanDefinition的克隆方法:
// 简化的使用示例
public class BeanDefinitionReader {
private final BeanDefinitionRegistry registry;
public void loadBeanDefinitions(Resource resource) {
// 解析XML或其他资源,创建原始BeanDefinition
GenericBeanDefinition originalDefinition = parseResource(resource);
// 如果存在父BeanDefinition,克隆并合并
if (originalDefinition.getParentName() != null) {
BeanDefinition parentDefinition = registry.getBeanDefinition(originalDefinition.getParentName());
// 克隆父定义
GenericBeanDefinition mergedDefinition = ((GenericBeanDefinition) parentDefinition).clone();
// 合并子定义的属性
mergeBeanDefinitions(mergedDefinition, originalDefinition);
registry.registerBeanDefinition(originalDefinition.getBeanName(), mergedDefinition);
} else {
registry.registerBeanDefinition(originalDefinition.getBeanName(), originalDefinition);
}
}
}
2. 原型模式在Spring中的价值
- 高效创建:通过克隆
BeanDefinition快速生成新的Bean定义,避免重复解析和初始化 - 动态调整:基于原型克隆后可以方便地修改部分属性,满足不同场景的需求
- 继承支持:实现Bean定义的继承关系,子定义可以基于父定义克隆并扩展
- 隔离性:克隆得到的新
BeanDefinition与原型相互独立,修改不会相互影响
六、总结
1. 原型模式的适用场景
- 当创建对象的成本较高(如需要复杂计算、IO操作或网络请求)时
- 当需要创建的对象与现有对象大部分属性相同,只有少数属性不同时
- 当需要动态生成对象,且无法提前确定对象的具体类型时
- 当需要避免使用构造函数创建对象,或构造函数参数复杂难以使用时
- 当需要为对象创建快照,保存对象在不同时间点的状态时
2. 原型模式与其他模式的区别
- 与工厂模式:工厂模式通过类的构造函数创建对象,原型模式通过复制现有对象创建新对象;工厂模式适合创建不同类型的对象,原型模式适合创建相似对象
- 与建造者模式:建造者模式注重复杂对象的分步构建过程,原型模式注重通过复制快速创建对象;建造者模式适合创建结构差异大的对象,原型模式适合创建结构相似的对象
3. 支付系统中的实践价值
- 提高批量处理效率:在批量退款、批量对账等场景中,通过复制原型对象快速生成大量相似对象
- 保证数据一致性:确保批量处理中共享信息的一致性,避免配置错误
- 简化复杂对象创建:对于支付请求、订单信息等复杂对象,通过复制避免重复初始化
- 支持状态快照:方便创建订单状态快照,用于问题排查和审计追踪
4. 实践建议
- 根据对象属性类型选择合适的拷贝方式:基本类型为主用浅拷贝,包含可变引用类型用深拷贝
- 封装克隆逻辑:通过专门的方法(如
createXXX)封装克隆+属性修改的完整流程,提高可用性 - 注意线程安全:在多线程环境下使用原型模式时,确保克隆过程的线程安全
- 避免过度使用:对于简单对象,直接创建可能比复制更简单高效
原型模式通过复制现有对象实现快速创建新对象,为支付系统中复杂对象的批量创建提供了高效解决方案,尤其适合处理相似对象的批量生成场景,能够显著提高系统效率和代码质量。