设计模式之原型模式

深入理解设计模式之原型模式(Prototype Pattern)

一、引言

在软件开发中,我们经常需要创建大量相似的对象。如果每次都通过new关键字创建,会面临以下问题:

  1. 创建成本高:对象初始化过程复杂,涉及大量计算或资源加载
  2. 性能问题:频繁创建相似对象导致性能下降
  3. 代码重复:需要重复设置相同的属性值
  4. 构造函数限制:有些对象的创建过程无法通过构造函数完全控制

想象这样的场景:你正在开发一个文档编辑器,用户需要复制一个包含格式、样式、内容的复杂文档。如果每次都重新创建并设置所有属性,不仅代码复杂,性能也会很差。更好的方式是直接复制现有文档,这就是原型模式的核心思想。

原型模式就像现实中的复印机:你有一份原始文件,想要多份相同的副本,只需要把原件放入复印机,按下复制按钮,就能快速得到多个副本,而不需要重新编写每份文件。

本文将深入探讨原型模式的原理、浅克隆与深克隆的区别,并结合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 核心要点

  1. 原型模式的本质:通过克隆现有对象创建新对象,避免使用new
  2. 两种克隆方式
    • 浅克隆:复制对象本身,引用类型共享
    • 深克隆:递归复制所有字段,完全独立
  3. 适用场景
    • 对象创建成本高
    • 需要大量相似对象
    • 需要保存对象状态
    • 避免复杂的构造过程

8.2 使用建议

arduino 复制代码
选择原型模式的检查清单:

✓ 对象创建成本是否很高?
✓ 是否需要创建大量相似对象?
✓ 构造函数是否过于复杂?
✓ 是否需要保存对象快照?

如果有2个以上"是",建议使用原型模式!

8.3 克隆方式选择

diff 复制代码
浅克隆:
- 对象只包含基本类型
- 引用类型是不可变对象(如String)
- 可以接受引用共享

深克隆:
- 对象包含可变引用类型
- 需要完全独立的副本
- 简单对象用手动克隆
- 复杂对象用序列化克隆

8.4 与其他模式的对比

diff 复制代码
原型 vs 工厂方法:
- 原型:克隆现有对象
- 工厂:创建新对象

原型 vs 单例:
- 原型:创建多个副本
- 单例:只有一个实例

原型 vs 建造者:
- 原型:复制整个对象
- 建造者:逐步构建对象

8.5 实践经验

  1. 优先使用浅克隆:简单高效,满足大多数场景
  2. 谨慎实现深克隆:手动深克隆容易遗漏字段
  3. 考虑序列化克隆:对于复杂对象,序列化是最简单的深克隆方式
  4. 注意不可克隆对象:如Thread、Socket等,需要特殊处理
  5. Spring使用prototype:在Spring中使用prototype作用域实现原型模式

参考资料

  1. 《设计模式:可复用面向对象软件的基础》- Gang of Four
  2. 《Effective Java》第三版 - Joshua Bloch(第13条:谨慎地覆盖clone)
  3. JDK源码(Object.clone()、ArrayList.clone())
  4. Spring Framework源码(prototype作用域)
  5. Apache Commons Lang源码(SerializationUtils)

相关推荐
LengineerC1 小时前
Rust仿node事件总线的简单实现
设计模式·rust
IT永勇2 小时前
C++设计模式-装饰器模式
c++·设计模式·装饰器模式
口袋物联9 小时前
设计模式之工厂模式在 C 语言中的应用(含 Linux 内核实例)
linux·c语言·设计模式·简单工厂模式
phdsky11 小时前
【设计模式】建造者模式
c++·设计模式·建造者模式
小毛驴85011 小时前
软件设计模式-装饰器模式
python·设计模式·装饰器模式
phdsky14 小时前
【设计模式】代理模式
设计模式·代理模式
ZHE|张恒1 天前
设计模式(十二)代理模式 — 用代理控制访问,实现延迟加载、权限控制等功能
设计模式·代理模式
SakuraOnTheWay1 天前
《深入设计模式》学习(1)—— 深入理解OOP中的6种对象关系
设计模式
q***71851 天前
Java进阶-SpringCloud设计模式-工厂模式的设计与详解
java·spring cloud·设计模式