Skip to content

原型模式 (Prototype Pattern)

一、定义

一句话概括:用原型实例指定创建对象的种类,并通过拷贝这些原型来创建新的对象。

官方定义(GoF):Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

原型模式的核心思想是:通过克隆(Clone)已有对象来创建新对象,而不是通过 new。当对象的创建成本很高(如需要大量数据库查询或网络请求)时,克隆一个已有对象比重新创建高效得多。


二、解决的问题

2.1 什么场景下需要原型模式?

  • 对象创建成本高:创建对象需要大量资源(数据库查询、网络请求、复杂计算)
  • 需要动态创建对象:运行时才知道要创建什么类型的对象
  • 需要避免复杂的工厂层次:相比工厂模式,原型模式只需一个 clone() 方法
  • 需要保存对象状态快照:如撤销操作、状态回滚
  • 需要大量相似对象:如游戏中的怪物、子弹,只需克隆模板然后微调

2.2 不用原型模式会有什么问题?

java
// 问题场景:创建一份报表需要从数据库查询大量数据
public class Report {
    private List<SalesData> salesData; // 从数据库查询的结果
    private List<UserBehavior> userBehavior; // 从数据库查询的结果
    private Map<String, Config> configs; // 从配置中心加载

    // 创建一份报表需要 5 秒(数据库查询 + 网络请求)
    public Report() {
        loadFromDatabase();  // 耗时 3 秒
        loadFromConfigCenter(); // 耗时 2 秒
    }
}

// 如果需要创建 100 份相似的报表(只修改标题),需要 500 秒!
// 使用原型模式:先创建一份,然后克隆 99 份,只需 5 秒 + 克隆时间

三、结构

3.1 文字描述

原型模式包含三个角色:

  • Prototype(抽象原型):声明克隆方法,通常是 Cloneable 接口或自定义接口
  • ConcretePrototype(具体原型):实现克隆方法,返回自身的副本
  • Client(客户端):通过调用原型的 clone() 方法创建新对象

3.2 ASCII 类图

┌──────────────────────────┐
│    <<interface>>          │
│      Prototype            │
├──────────────────────────┤
│ + clone(): Prototype      │
└──────────────────────────┘


┌───────────────────────┐        ┌───────────────────────┐
│  ConcretePrototypeA    │        │  ConcretePrototypeB    │
├───────────────────────┤        ├───────────────────────┤
│ - field1: String       │        │ - field1: int          │
│ - field2: List<String> │        │ - field2: Date         │
├───────────────────────┤        ├───────────────────────┤
│ + clone(): Prototype   │        │ + clone(): Prototype   │
│   (深拷贝/浅拷贝)       │        │   (深拷贝/浅拷贝)       │
└───────────────────────┘        └───────────────────────┘

3.3 原型管理器(Prototype Registry)

┌──────────────────────────────┐
│    PrototypeRegistry          │
├──────────────────────────────┤
│ - prototypes: Map<String, Proto>│
├──────────────────────────────┤
│ + register(key, proto): void │
│ + unregister(key): void      │
│ + clone(key): Prototype      │
└──────────────────────────────┘

四、代码实现

4.1 基础实现

4.1.1 浅克隆(Shallow Clone)

java
/**
 * 浅克隆 —— 实现 Cloneable 接口
 *
 * 浅克隆特点:
 * - 基本类型字段:复制值
 * - 引用类型字段:复制引用(指向同一个对象)
 * - 修改克隆对象的引用字段会影响原对象!
 */
public class ShallowCloneDemo {

    public static class Document implements Cloneable {
        private String title;
        private List<String> paragraphs;  // 引用类型
        private Date createdAt;

        public Document(String title, List<String> paragraphs) {
            this.title = title;
            this.paragraphs = paragraphs;
            this.createdAt = new Date();
        }

        // ========== 浅克隆实现 ==========
        @Override
        public Document clone() {
            try {
                // Object.clone() 是浅克隆,逐字段复制
                return (Document) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new RuntimeException("克隆失败", e);
            }
        }

        public String getTitle() { return title; }
        public void setTitle(String title) { this.title = title; }
        public List<String> getParagraphs() { return paragraphs; }
        public Date getCreatedAt() { return createdAt; }

        @Override
        public String toString() {
            return "Document{title='" + title + "', paragraphs=" + paragraphs +
                   ", createdAt=" + createdAt + "}";
        }
    }

    public static void main(String[] args) {
        List<String> originalParagraphs = new ArrayList<>();
        originalParagraphs.add("这是第一段内容");
        originalParagraphs.add("这是第二段内容");

        Document original = new Document("原始文档", originalParagraphs);
        Document cloned = original.clone();

        System.out.println("===== 克隆后 =====");
        System.out.println("原始: " + original);
        System.out.println("克隆: " + cloned);

        // 修改克隆对象的字段
        cloned.setTitle("克隆文档");
        cloned.getParagraphs().add("这是克隆添加的段落");

        System.out.println("\n===== 修改克隆对象后 =====");
        System.out.println("原始: " + original);
        System.out.println("克隆: " + cloned);

        // 注意:paragraphs 被共享了!修改克隆对象影响了原对象
        System.out.println("\n【浅克隆问题】paragraphs 引用是否相同?"
                + (original.getParagraphs() == cloned.getParagraphs()));
    }
}

4.1.2 深克隆(Deep Clone)

java
/**
 * 深克隆 —— 递归克隆所有引用类型字段
 *
 * 深克隆特点:
 * - 基本类型字段:复制值
 * - 引用类型字段:递归复制,创建全新的对象
 * - 克隆对象和原对象完全独立,互不影响
 */
public class DeepCloneDemo {

    public static class Document implements Cloneable {
        private String title;
        private List<String> paragraphs;
        private Date createdAt;
        private Author author;  // 嵌套引用类型

        public Document(String title, List<String> paragraphs, Author author) {
            this.title = title;
            this.paragraphs = paragraphs;
            this.createdAt = new Date();
            this.author = author;
        }

        // ========== 深克隆实现 ==========
        @Override
        public Document clone() {
            try {
                // 1. 先进行浅克隆
                Document cloned = (Document) super.clone();

                // 2. 对每个引用类型字段进行深拷贝
                cloned.paragraphs = new ArrayList<>(this.paragraphs); // 新建 List
                cloned.createdAt = (Date) this.createdAt.clone();     // 克隆 Date
                cloned.author = this.author.clone();                  // 克隆 Author

                return cloned;
            } catch (CloneNotSupportedException e) {
                throw new RuntimeException("克隆失败", e);
            }
        }

        // Getters & Setters
        public String getTitle() { return title; }
        public void setTitle(String title) { this.title = title; }
        public List<String> getParagraphs() { return paragraphs; }
        public Date getCreatedAt() { return createdAt; }
        public Author getAuthor() { return author; }

        @Override
        public String toString() {
            return "Document{title='" + title + "', paragraphs=" + paragraphs +
                   ", createdAt=" + createdAt + ", author=" + author + "}";
        }
    }

    // ========== 嵌套对象(也需要实现深克隆) ==========
    public static class Author implements Cloneable {
        private String name;
        private String email;

        public Author(String name, String email) {
            this.name = name;
            this.email = email;
        }

        @Override
        public Author clone() {
            try {
                return (Author) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new RuntimeException("克隆失败", e);
            }
        }

        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public String getEmail() { return email; }
        public void setEmail(String email) { this.email = email; }

        @Override
        public String toString() {
            return "Author{name='" + name + "', email='" + email + "'}";
        }
    }

    public static void main(String[] args) {
        List<String> originalParagraphs = new ArrayList<>();
        originalParagraphs.add("第一段内容");
        originalParagraphs.add("第二段内容");

        Author originalAuthor = new Author("张三", "zhangsan@example.com");

        Document original = new Document("原始文档", originalParagraphs, originalAuthor);
        Document cloned = original.clone();

        System.out.println("===== 克隆后 =====");
        System.out.println("原始: " + original);
        System.out.println("克隆: " + cloned);

        // 修改克隆对象
        cloned.setTitle("克隆文档");
        cloned.getParagraphs().add("克隆添加的段落");
        cloned.getAuthor().setName("李四");

        System.out.println("\n===== 修改克隆对象后 =====");
        System.out.println("原始: " + original);
        System.out.println("克隆: " + cloned);

        // 验证独立性
        System.out.println("\n【深克隆验证】");
        System.out.println("paragraphs 引用是否相同? "
                + (original.getParagraphs() == cloned.getParagraphs()));
        System.out.println("author 引用是否相同?     "
                + (original.getAuthor() == cloned.getAuthor()));
    }
}

4.2 进阶实现

4.2.1 序列化深克隆(通用方案)

java
import java.io.*;

/**
 * 通过序列化实现深克隆 —— 最通用的深克隆方案
 *
 * 优点:
 * - 不需要手动处理每个引用字段
 * - 自动处理嵌套对象、集合、Map 等复杂结构
 * - 适用于任何实现了 Serializable 接口的对象
 *
 * 缺点:
 * - 性能较低(涉及序列化/反序列化 I/O 操作)
 * - 所有字段必须实现 Serializable
 * - transient 字段不会被克隆
 */
public class SerializationDeepClone {

    public static class ComplexObject implements Serializable {
        private static final long serialVersionUID = 1L;

        private String name;
        private List<String> tags;
        private Map<String, Object> metadata;
        private NestedObject nested;

        public ComplexObject(String name) {
            this.name = name;
            this.tags = new ArrayList<>();
            this.metadata = new HashMap<>();
            this.nested = new NestedObject("nested-" + name);
        }

        // ========== 使用序列化实现深克隆 ==========
        @SuppressWarnings("unchecked")
        public ComplexObject deepClone() {
            try {
                // 序列化到字节数组
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(bos);
                oos.writeObject(this);
                oos.close();

                // 从字节数组反序列化
                ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bis);
                return (ComplexObject) ois.readObject();
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException("序列化深克隆失败", e);
            }
        }

        // ========== 通用深克隆工具方法 ==========
        @SuppressWarnings("unchecked")
        public static <T extends Serializable> T deepClone(T object) {
            try {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(bos);
                oos.writeObject(object);
                oos.close();

                ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bis);
                return (T) ois.readObject();
            } catch (IOException | ClassNotFoundException e) {
                throw new RuntimeException("序列化深克隆失败", e);
            }
        }

        // Getters
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public List<String> getTags() { return tags; }
        public Map<String, Object> getMetadata() { return metadata; }
        public NestedObject getNested() { return nested; }

        @Override
        public String toString() {
            return "ComplexObject{name='" + name + "', tags=" + tags +
                   ", metadata=" + metadata + ", nested=" + nested + "}";
        }
    }

    public static class NestedObject implements Serializable {
        private static final long serialVersionUID = 1L;
        private String value;

        public NestedObject(String value) { this.value = value; }
        public String getValue() { return value; }
        public void setValue(String value) { this.value = value; }

        @Override
        public String toString() {
            return "NestedObject{value='" + value + "'}";
        }
    }

    public static void main(String[] args) {
        ComplexObject original = new ComplexObject("原型");
        original.getTags().add("tag1");
        original.getMetadata().put("key1", "value1");

        // 使用序列化深克隆
        ComplexObject cloned = SerializationDeepClone.deepClone(original);

        System.out.println("原始: " + original);
        System.out.println("克隆: " + cloned);

        // 修改克隆对象,验证独立性
        cloned.setName("克隆副本");
        cloned.getTags().add("tag2");
        cloned.getNested().setValue("modified");

        System.out.println("\n修改后 - 原始: " + original);
        System.out.println("修改后 - 克隆: " + cloned);

        System.out.println("\nnested 引用是否相同? "
                + (original.getNested() == cloned.getNested()));
    }
}

4.2.2 原型管理器(Prototype Registry)

java
/**
 * 原型管理器 —— 维护一个原型注册表,通过 key 获取克隆副本
 *
 * 场景:Spring 中 prototype 作用域的 Bean 创建
 */
public class PrototypeRegistry {

    // ========== 原型接口 ==========
    public interface Prototype extends Cloneable {
        Prototype clone();
        String getType();
    }

    // ========== 具体原型 ==========
    public static class Circle implements Prototype {
        private int radius;
        private String color;

        public Circle(int radius, String color) {
            this.radius = radius;
            this.color = color;
        }

        @Override
        public Circle clone() {
            try {
                return (Circle) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public String getType() { return "CIRCLE"; }

        public void setRadius(int radius) { this.radius = radius; }
        public void setColor(String color) { this.color = color; }

        @Override
        public String toString() {
            return "Circle{radius=" + radius + ", color='" + color + "'}";
        }
    }

    public static class Rectangle implements Prototype {
        private int width;
        private int height;
        private String color;

        public Rectangle(int width, int height, String color) {
            this.width = width;
            this.height = height;
            this.color = color;
        }

        @Override
        public Rectangle clone() {
            try {
                return (Rectangle) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public String getType() { return "RECTANGLE"; }

        public void setWidth(int width) { this.width = width; }
        public void setHeight(int height) { this.height = height; }

        @Override
        public String toString() {
            return "Rectangle{width=" + width + ", height=" + height + ", color='" + color + "'}";
        }
    }

    // ========== 原型注册表 ==========
    public static class Registry {
        private final Map<String, Prototype> prototypes = new HashMap<>();

        public void register(Prototype prototype) {
            prototypes.put(prototype.getType(), prototype);
        }

        public void unregister(String type) {
            prototypes.remove(type);
        }

        public Prototype create(String type) {
            Prototype prototype = prototypes.get(type);
            if (prototype == null) {
                throw new IllegalArgumentException("未注册的原型类型: " + type);
            }
            return prototype.clone();
        }
    }

    // ========== 测试 ==========
    public static void main(String[] args) {
        Registry registry = new Registry();

        // 注册原型模板
        registry.register(new Circle(10, "红色"));
        registry.register(new Rectangle(20, 10, "蓝色"));

        // 通过克隆创建新对象
        Circle circle1 = (Circle) registry.create("CIRCLE");
        Circle circle2 = (Circle) registry.create("CIRCLE");
        Rectangle rect1 = (Rectangle) registry.create("RECTANGLE");

        // 修改克隆对象的属性
        circle2.setRadius(20);
        circle2.setColor("绿色");

        System.out.println("Circle 1: " + circle1);
        System.out.println("Circle 2: " + circle2);
        System.out.println("Rectangle 1: " + rect1);

        System.out.println("\ncircle1 和 circle2 是同一个对象吗? " + (circle1 == circle2));
    }
}

4.3 生产级实现(Spring Boot 场景)

java
import java.util.concurrent.ConcurrentHashMap;

/**
 * 生产级报表模板管理器 —— 原型模式 + Spring Boot
 *
 * 业务场景:报表系统需要生成大量相似报表,每个报表从数据库查询数据成本很高
 * 解决方案:先创建一份模板报表(加载所有数据),后续报表通过克隆模板生成
 */
public class ReportTemplateManager {

    // ========== 报表产品 ==========
    public static class Report implements Cloneable {
        private String reportId;
        private String title;
        private String author;
        private List<SalesData> salesData;     // 从数据库查询(成本高)
        private Map<String, Object> configs;   // 从配置中心加载(成本高)
        private Date generatedAt;

        public Report(String reportId, String title, String author) {
            this.reportId = reportId;
            this.title = title;
            this.author = author;
            this.generatedAt = new Date();
        }

        public void loadSalesData() {
            // 模拟从数据库加载大量数据(耗时操作)
            System.out.println("  [Report] 从数据库加载销售数据...");
            this.salesData = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                salesData.add(new SalesData("PROD-" + i, Math.random() * 1000));
            }
        }

        public void loadConfigs() {
            // 模拟从配置中心加载配置
            System.out.println("  [Report] 从配置中心加载配置...");
            this.configs = new ConcurrentHashMap<>();
            configs.put("company", "ABC Corp");
            configs.put("currency", "CNY");
            configs.put("timezone", "Asia/Shanghai");
        }

        // ========== 深克隆 ==========
        @Override
        public Report clone() {
            try {
                Report cloned = (Report) super.clone();
                // 深拷贝引用类型
                cloned.salesData = new ArrayList<>(this.salesData);
                cloned.configs = new ConcurrentHashMap<>(this.configs);
                cloned.generatedAt = new Date();
                return cloned;
            } catch (CloneNotSupportedException e) {
                throw new RuntimeException("克隆失败", e);
            }
        }

        public void setTitle(String title) { this.title = title; }
        public void setReportId(String reportId) { this.reportId = reportId; }

        @Override
        public String toString() {
            return "Report{id='" + reportId + "', title='" + title +
                   "', author='" + author + "', dataSize=" +
                   (salesData != null ? salesData.size() : 0) +
                   ", configs=" + configs + "}";
        }
    }

    public static class SalesData {
        private String productId;
        private double amount;
        public SalesData(String productId, double amount) {
            this.productId = productId;
            this.amount = amount;
        }
    }

    // ========== 报表模板管理器 ==========
    public static class ReportTemplateRegistry {
        // 缓存已创建的模板报表
        private final Map<String, Report> templates = new ConcurrentHashMap<>();

        /**
         * 创建并缓存模板报表(只执行一次高成本的数据加载)
         */
        public Report createTemplate(String templateKey, String author) {
            return templates.computeIfAbsent(templateKey, key -> {
                System.out.println("===== 创建模板报表: " + key + " =====");
                Report template = new Report("TEMPLATE-" + key, "模板报表", author);
                template.loadSalesData();  // 高成本操作,只执行一次
                template.loadConfigs();     // 高成本操作,只执行一次
                System.out.println("===== 模板报表创建完成 =====\n");
                return template;
            });
        }

        /**
         * 基于模板生成新报表(克隆 + 微调)
         */
        public Report generateReport(String templateKey, String reportId, String title) {
            Report template = templates.get(templateKey);
            if (template == null) {
                throw new IllegalArgumentException("模板不存在: " + templateKey);
            }

            // 克隆模板(避免了重复的数据加载)
            Report report = template.clone();
            report.setReportId(reportId);
            report.setTitle(title);
            return report;
        }
    }

    // ========== 测试 ==========
    public static void main(String[] args) {
        ReportTemplateRegistry registry = new ReportTemplateRegistry();

        // 第一次创建模板(耗时较长)
        long start = System.currentTimeMillis();
        registry.createTemplate("monthly_sales", "张三");
        System.out.println("模板创建耗时: " + (System.currentTimeMillis() - start) + "ms\n");

        // 基于模板快速生成多份报表(几乎瞬间完成)
        start = System.currentTimeMillis();
        Report report1 = registry.generateReport("monthly_sales", "RPT-001", "2024年1月销售报表");
        Report report2 = registry.generateReport("monthly_sales", "RPT-002", "2024年2月销售报表");
        Report report3 = registry.generateReport("monthly_sales", "RPT-003", "2024年3月销售报表");
        System.out.println("3份报表生成耗时: " + (System.currentTimeMillis() - start) + "ms\n");

        System.out.println(report1);
        System.out.println(report2);
        System.out.println(report3);
    }
}

五、优缺点

优点

优点说明
性能提升克隆比 new 创建代价高的对象快得多,尤其是涉及 I/O 操作时
简化对象创建避免复杂的初始化过程,直接克隆已有对象
动态创建运行时动态决定创建哪种类型的对象,比工厂模式更灵活
避免工厂层次不需要为每种产品创建对应的工厂类
保存状态快照可用于实现撤销/重做功能

缺点

缺点说明
深克隆复杂每个类都需要实现 clone(),且需处理嵌套对象的深拷贝
Cloneable 接口问题Java 的 Cloneable 接口没有定义 clone() 方法,使用不便
循环引用问题存在循环引用时,深克隆可能导致无限递归或栈溢出
克隆与构造器的区别克隆不调用构造器,可能导致某些初始化逻辑被跳过

六、适用场景

  1. 对象创建成本高:如包含大量数据库查询结果、网络请求结果的对象
  2. 需要大量相似对象:如游戏中的敌人、子弹、粒子效果
  3. 需要保存对象状态:如撤销/重做(Undo/Redo)、事务回滚
  4. 动态配置:从配置文件中读取模板,运行时克隆生成具体配置
  5. 避免工厂层次爆炸:当产品种类很多时,用原型模式替代工厂模式
  6. Spring Prototype Bean:每次请求获取 prototype Bean 时,Spring 会创建新实例
  7. 缓存系统:缓存序列化/反序列化后的对象副本

七、JDK / Spring 框架中的实际应用

7.1 JDK 中的原型模式

1. java.lang.Object.clone()
   - 所有对象的克隆能力都源于此方法
   - 是浅克隆,需要深克隆时需手动实现

2. java.lang.Cloneable
   - 标记接口,标识该类支持克隆
   - 不实现 Cloneable 调用 clone() 会抛 CloneNotSupportedException

3. java.util.ArrayList.clone()
   - ArrayList 实现了深克隆(元素本身是浅克隆)
   - 新 ArrayList 和原 ArrayList 的 elementData 是不同的数组

4. java.util.HashMap.clone()
   - HashMap 实现了深克隆
   - 新的 table 数组 + 新的 entry 节点

5. java.util.Date.clone()
   - Date 实现了克隆,返回新的 Date 对象

7.2 Spring 框架中的原型模式

1. @Scope("prototype")
   - Spring 的 prototype 作用域 Bean
   - 每次 getBean() 都会创建新实例(本质是原型模式)

2. AbstractBeanDefinition
   - Spring 内部使用 BeanDefinition 作为原型来描述 Bean 的元数据
   - 真正创建 Bean 时,根据 BeanDefinition 原型 + 依赖注入创建实例

3. ObjectMapper (Jackson)
   - 虽然 ObjectMapper 不是原型模式的标准实现,但 Spring 中常将其作为单例
   - 通过 readValue() 反序列化时,本质是根据类模板(原型)创建对象

4. PrototypeBeanNameGenerator
   - Spring 的 Bean 命名生成器,与原型模式概念相关

7.3 源码示例:Spring Prototype Bean

java
// Spring 源码中 AbstractBeanFactory 的 doGetBean 方法(简化)
protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly) {
    // 获取 BeanDefinition(原型元数据)
    RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

    // 如果是 prototype 作用域
    if (mbd.isPrototype()) {
        // 每次都会创建新的 Bean 实例(原型模式)
        Object prototypeInstance = createBean(beanName, mbd, args);
        return (T) prototypeInstance;
    }
    // 如果是 singleton 作用域
    else {
        // 从单例缓存中获取
        Object singletonInstance = getSingleton(beanName);
        return (T) singletonInstance;
    }
}

八、与其他模式的关系

相关模式关系说明
工厂方法模式工厂方法创建新对象,原型模式克隆已有对象。工厂方法通常用于创建层次结构中的对象,原型模式用于创建相似对象
抽象工厂模式抽象工厂可以用原型模式实现:工厂维护原型对象,通过克隆创建新实例
单例模式原型模式与单例模式相反:单例限制唯一实例,原型模式鼓励克隆创建多个实例
组合模式组合模式的树形结构常使用原型模式来克隆复杂节点
备忘录模式备忘录模式使用原型模式保存对象状态快照,用于撤销操作
建造者模式建造者分步构建,原型一步克隆。两者可以组合:先克隆原型,再用建造者修改

九、面试常见问题

Q1:Java 中浅克隆和深克隆的区别?

答案

对比维度浅克隆(Shallow Clone)深克隆(Deep Clone)
基本类型字段复制值复制值
引用类型字段复制引用(指向同一个对象)递归复制(创建新对象)
内存独立性克隆对象和原对象共享引用类型完全独立,互不影响
实现方式super.clone()手动递归克隆 / 序列化
性能
风险修改克隆对象的引用字段会影响原对象

Q2:Java 中 Cloneable 接口为什么不包含 clone() 方法?

答案: 这是一个经典的 Java 设计缺陷(Joshua Bloch 在《Effective Java》中也有提及):

  1. Cloneable 是一个标记接口(Marker Interface),不包含任何方法
  2. clone() 方法定义在 Object 类中,且是 protected
  3. 如果 Cloneable 包含了 clone() 方法,那么所有实现类都必须实现它,但这种设计在 Java 1.0 时没有采用
  4. 这种设计导致:如果不实现 Cloneable 就调用 clone(),会抛出 CloneNotSupportedException

最佳实践:使用拷贝构造器序列化深克隆来替代 Cloneable


Q3:为什么《Effective Java》建议使用拷贝构造器替代 clone()?

答案: Joshua Bloch 认为 Cloneable 接口存在以下问题:

  1. 接口不包含方法:无法通过接口调用 clone()
  2. 实现约束不明确:不实现 Cloneable 会抛异常,但编译期无法检查
  3. final 字段问题clone()final 字段不兼容
  4. 浅克隆默认行为Object.clone() 默认是浅克隆,容易出错
  5. 不调用构造器:克隆不调用构造器,可能导致初始化逻辑被跳过

推荐的替代方案

java
// 方式1:拷贝构造器
public class Document {
    private String title;
    private List<String> paragraphs;

    // 拷贝构造器
    public Document(Document other) {
        this.title = other.title;
        this.paragraphs = new ArrayList<>(other.paragraphs); // 深拷贝
    }
}

// 方式2:静态工厂方法
public static Document copyOf(Document other) {
    Document copy = new Document();
    copy.setTitle(other.getTitle());
    copy.setParagraphs(new ArrayList<>(other.getParagraphs()));
    return copy;
}

Q4:原型模式和 Spring 的 prototype scope 有什么关系?

答案: Spring 的 prototype scope 本质就是原型模式的应用:

  • 每次调用 getBean() 时,Spring 会根据 BeanDefinition(原型元数据)创建新的 Bean 实例
  • 这与原型模式中"通过克隆原型创建新对象"的思想一致
  • 区别在于:Spring 通过反射 + 依赖注入创建新实例,而不是通过 clone() 方法
  • Spring 的 singleton scope 是单例模式,prototype scope 是原型模式

Q5:原型模式在什么情况下比工厂模式更好?

答案

  1. 产品种类极多:工厂模式需要为每种产品创建工厂类,原型模式只需一个原型对象
  2. 对象创建成本高:克隆比 new + 初始化快得多
  3. 运行时动态决定:工厂模式在编译期确定产品类型,原型模式在运行时动态克隆
  4. 需要保存状态:原型可以保存当前状态,工厂只能创建初始状态的对象
  5. 避免类层次爆炸:当产品类数量庞大时,工厂类数量也会爆炸

总结

原型模式通过克隆已有对象来创建新对象,核心要点:

  1. 浅克隆 vs 深克隆:根据是否需要独立修改引用类型字段来选择
  2. 序列化深克隆是最通用的深克隆方案,但性能较差
  3. 拷贝构造器Cloneable 的推荐替代方案
  4. 原型管理器维护一个原型对象注册表,按需克隆
  5. Spring 的 prototype scope 是原型模式在生产环境中的典型应用
  6. 原型模式特别适合创建成本高需要大量相似对象的场景