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

摘要

本文详细介绍了原型设计模式,这是一种创建型设计模式,通过复制现有对象(原型)来创建新对象,避免使用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 只负责创建,不管理销毁 |
| 适用场景 | 共享服务、无状态组件 | 有状态对象、频繁创建的临时对象 |

博文参考

相关推荐
哆啦A梦的口袋呀1 小时前
基于Python学习《Head First设计模式》 第一章 策略模式
python·学习·设计模式
冰茶_18 小时前
适配器模式:让不兼容接口协同工作
microsoft·设计模式·适配器模式
magic 24518 小时前
Java设计模式详解:策略模式(Strategy Pattern)
java·设计模式·策略模式
琢磨先生David19 小时前
从模式到架构:Java 工厂模式的设计哲学与工程化实践
java·设计模式
庄小焱20 小时前
设计模式——状态设计模式(行为型)
设计模式
庄小焱20 小时前
设计模式——责任链设计模式(行为型)
设计模式
勤奋的知更鸟20 小时前
Java抽象工厂模式详解
设计模式
季鸢1 天前
Java设计模式之迭代器模式详解
java·设计模式·迭代器模式
NorthCastle1 天前
设计模式-行为型模式-模版方法模式
java·设计模式·模板方法模式
庄小焱1 天前
设计模式——观察者设计模式(行为型)
设计模式