设计模式——原型设计模式(创建型)

摘要

本文详细介绍了原型设计模式,这是一种创建型设计模式,通过复制现有对象(原型)来创建新对象,避免使用new关键字,可提高性能并简化对象创建逻辑。文章阐述了其优点,如提高性能、动态扩展和简化构造逻辑,以及缺点,如深拷贝实现复杂、复杂资源复制困难和易破坏封装性。还介绍了使用条件、结构、实现方式、适合场景和实战示例,并探讨了Spring中原型思想的应用。

1. 原型设计模式定义

原型设计模式 是一种创建型设计模式 ,其核心思想是:通过复制现有的对象(原型)来创建新对象,而不是通过 new****关键字来实例化,从而提高性能并简化对象创建逻辑。使用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象。

|------|-----------------------------------------|
| 特性 | 说明 |
| 创建方式 | 使用已有对象(原型)复制创建,而不是 new |
| 拷贝类型 | 通常支持 浅拷贝深拷贝 |
| 接口 | 通常实现 Cloneable接口,重写 clone()方法(Java) |
| 性能 | 适合对象创建代价高、结构复杂的场景 |

1.1. ✅ 原型模式的优点

  1. 提高性能:避免重复创建复杂对象。
  2. 动态扩展:在运行时动态创建对象。
  3. 简化构造逻辑:不用关心构造细节,只需拷贝。

1.2. ❌ 缺点

  • 深拷贝实现复杂(需注意引用对象)
  • 对象包含复杂资源(如数据库连接)时不易复制
  • 克隆过程容易破坏封装性

1.3. 📌 原型设计模式使用条件

适合以下场景:

|----------|--------------------|
| 场景 | 说明 |
| 对象构造开销大 | 如数据库连接、网络通信等初始化代价高 |
| 对象构造逻辑复杂 | 包含多个参数、状态配置等 |
| 多个相似对象 | 只需要改变少部分属性 |

2. 原型设计模式结构

2.1. 原型设计模式类图

  1. 客户(Client)角色:客户类提出创建对象的请求。
  2. 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个Java接口或Java抽象类实现。此角色给出所有的具体原型类所需的接口。
  3. 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象的原型角色所要求的接口

2.2. 原型设计模式时序图

3. 原型设计模式实现方式

原型设计模式的实现方式核心在于 ------ 通过克隆现有对象来创建新对象 ,而不是使用 new。在 Java 中,通常通过实现 Cloneable 接口并重写 clone() 方法来完成。下面是常见的两种实现方式:浅拷贝和深拷贝。

3.1. 🛠️ 原型模式实现方式

|---------|-------------------------------|
| 实现方式 | 描述 |
| 浅拷贝 | 复制对象本身,但对象中的引用类型字段只复制地址(共享引用) |
| 深拷贝 | 复制对象及其引用对象(创建完全独立的新对象) |

3.2. ✅ 浅拷贝示例(实现 Cloneable)

复制代码
@Data
public class Person implements Cloneable {
    private String name;
    private int age;

    @Override
    public Person clone() {
        try {
            return (Person) super.clone(); // 浅拷贝
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

✅ 浅拷贝适用于字段都是基本类型或不可变类型(如 String)。

3.3. ✅ 深拷贝示例(引用字段也复制)

复制代码
@Data
public class Person implements Cloneable {
    private String name;
    private int age;
    private Address address; // 引用类型

    @Override
    public Person clone() {
        try {
            Person cloned = (Person) super.clone();
            // 深拷贝 Address 对象
            cloned.address = address.clone();
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

@Data
class Address implements Cloneable {
    private String city;

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

3.4. 🔁 使用序列化实现深拷贝(通用方案)

复制代码
public static <T extends Serializable> T deepCopy(T obj) {
    try (
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos)
    ) {
        oos.writeObject(obj);
        try (
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis)
        ) {
            return (T) ois.readObject();
        }
    } catch (Exception e) {
        throw new RuntimeException("深拷贝失败", e);
    }
}

3.5. 📌 实现步骤总结

|---------------------------|-----------------------------|
| 步骤 | 描述 |
| 1. 实现 Cloneable接口 | 否则调用 clone()会抛异常 |
| 2. 重写 clone()方法 | 调用 super.clone()并处理引用类型字段 |
| 3. 深拷贝时递归调用子对象的 clone() | 保证对象间完全独立 |

3.6. 💡 Tips(在项目中使用时注意):

  • 不要忘了处理 引用类型字段(深拷贝)
  • 可以用工具类如 Apache Commons Lang 的 SerializationUtils.clone() 简化处理
  • Java record 类型天然不可变,结合原型模式更安全
  • Spring 中 @Scope("prototype") 是原型思想的一种应用

4. 原型设计模式适合场景

原型设计模式(Prototype Pattern)适用于通过复制(克隆)现有对象来快速创建新对象的场景。以下是它适用和不适用的场景对比:

4.1. ✅ 适合使用原型模式的场景

|---------------------|---------------------------------------|
| 场景 | 说明 |
| 对象创建成本高 | 构造函数复杂、耗时(如 I/O、数据库、复杂计算)时,通过克隆避免重复创建 |
| 对象初始化复杂 | 需要设置很多配置/参数,克隆一个已初始化好的对象更方便 |
| 需要大量相似对象 | 如游戏中怪物/兵种生成、工作流中节点复制、表单复制等 |
| 运行时动态创建对象 | 不依赖类名和构造函数,只要能访问 prototype 对象即可 |
| 避免工厂或 new 创建的耦合 | 提高系统灵活性,符合开闭原则 |

4.2. ❌ 不适合使用原型模式的场景

|-------------------|---------------------------|
| 场景 | 原因 |
| 对象结构简单、创建成本低 | 使用 new即可,没必要增加 clone 成本 |
| 对象包含复杂的循环引用关系 | clone 实现困难,容易出错 |
| 频繁深拷贝,性能反而变差 | clone 比 new 还慢,得不偿失 |
| 对象包含外部不可复制资源 | 如线程、Socket、数据库连接等资源不能复制 |
| 需要严格控制对象创建流程 | 比如工厂方法模式更适合定制构造逻辑 |

4.3. 🧠 实际项目应用建议

|-------------------|------------------------|
| 建议 | 说明 |
| 复杂对象或批量对象创建场景优先考虑 | 如任务调度系统中复制任务模板 |
| 引用类型多的对象必须小心实现深拷贝 | 避免共享引用带来的副作用 |
| 尽量配合原型注册表(原型缓存池) | 管理所有 prototype 模板,集中创建 |

5. 原型设计模式实战示例

在 Spring 项目中,原型设计模式(Prototype Pattern)最典型的应用方式是使用 Spring 的 @Scope("prototype") 注解来声明一个 Bean 为原型模式,使得每次注入都会创建一个新实例。

5.1. ✅ 示例:批量生成任务模板对象(适合原型模式场景)

5.1.1. 定义一个任务对象(复杂初始化逻辑)

复制代码
@Data
public class Task implements Cloneable {
    private String name;
    private String type;
    private List<String> steps = new ArrayList<>();

    public Task(String name, String type) {
        this.name = name;
        this.type = type;
        this.steps.add("初始化");
        this.steps.add("执行中");
        this.steps.add("结束");
    }

    @Override
    public Task clone() {
        try {
            Task clone = (Task) super.clone();
            // 深拷贝
            clone.steps = new ArrayList<>(this.steps);
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

5.1.2. Spring容器中定义该对象为原型 Bean

复制代码
@Configuration
public class TaskConfig {

    @Bean
    @Scope("prototype") // 每次注入时都会创建新实例
    public Task taskPrototype() {
        return new Task("默认任务", "general");
    }
}

5.1.3. 任务服务中使用 Prototype Bean并进行克隆

复制代码
@Service
public class TaskService {

    @Autowired
    private ApplicationContext applicationContext;

    public Task createNewTask(String name, String type) {
        // 每次从容器获取都会是一个新实例(原型)
        Task prototype = applicationContext.getBean(Task.class);
        Task newTask = prototype.clone(); // 克隆一份再设置参数
        newTask.setName(name);
        newTask.setType(type);
        return newTask;
    }
}

5.1.4. ✅ 为什么这是原型模式适合的场景?

|-------------------|-----------------------|
| 点 | 说明 |
| 创建成本高 | 每个任务初始化流程复杂,创建频繁 |
| 需要批量创建不同配置的对象 | 每个任务有不同参数,复用模板快速创建 |
| 运行时动态变化 | 原型对象提供灵活性,可随时根据当前需求构造 |

6. 原型设计模式思考

6.1. Spring 中 @Scope("prototype") 是原型思想的一种应用?

使用 @Scope("prototype") 标注的 Bean,每次从 Spring 容器中获取时,都会创建一个全新的实例,而不是复用同一个对象。这就是"原型"的核心思想。

Prototype 模式的定义:"用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。" (即:通过复制一个"原型对象"来生成新对象

6.1.1. Spring 中如何实现这个思想?

|----------------|-------------------------------------|
| Prototype 模式要素 | Spring 的对应机制 |
| 原型对象 | Bean 的定义(类 + 配置) |
| 拷贝新对象 | 每次调用 getBean(),Spring 通过反射"创建新对象" |
| 统一管理原型 | 由 Spring 容器负责构造、注入依赖等生命周期管理 |

6.1.2. 示例

复制代码
@Component
@Scope("prototype")
public class Task {
    public Task() {
        System.out.println("新任务实例被创建");
    }
}

@Autowired
private ApplicationContext applicationContext;

public void createTasks() {
    Task t1 = applicationContext.getBean(Task.class); // 创建一个新实例
    Task t2 = applicationContext.getBean(Task.class); // 再创建一个新实例
    System.out.println(t1 == t2); // false
}

6.1.3. prototype与singleton 的区别

|--------|-----------------|--------------------|
| 特性 | singleton(默认) | prototype |
| 实例个数 | 全局一个 | 每次请求新建 |
| 生命周期管理 | Spring 管 | Spring 只负责创建,不管理销毁 |
| 适用场景 | 共享服务、无状态组件 | 有状态对象、频繁创建的临时对象 |

博文参考

相关推荐
触底反弹3 小时前
🤯 面试被问 AI Workflow 和 Agent 有啥区别?3 张图 + 2 段代码讲清楚!
人工智能·设计模式·面试
杨充1 天前
10.可测试性实战设计
设计模式·开源·代码规范
杨充1 天前
9.重构十二式的实战
设计模式·开源·代码规范
杨充1 天前
6.设计原则的全景图
设计模式·开源·全栈
杨充1 天前
2.面向对象的特性
设计模式
杨充1 天前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充1 天前
8.反模式与坏味道
设计模式·开源·代码规范
杨充1 天前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法