原型模式:设计与实践

17 阅读13分钟

原型模式:设计与实践

一、什么是原型模式

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内部在BeanDefinitionReaderBeanFactory等组件中广泛使用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)封装克隆+属性修改的完整流程,提高可用性
  • 注意线程安全:在多线程环境下使用原型模式时,确保克隆过程的线程安全
  • 避免过度使用:对于简单对象,直接创建可能比复制更简单高效

原型模式通过复制现有对象实现快速创建新对象,为支付系统中复杂对象的批量创建提供了高效解决方案,尤其适合处理相似对象的批量生成场景,能够显著提高系统效率和代码质量。