设计模式:原型模式(Prototype Pattern)java版本
设计模式:原型模式(Prototype Pattern)java版本
前言
在软件开发和系统重构的过程中,我们经常会遇到创建复杂对象导致性能瓶颈的问题。
例如,当一个对象的初始化需要消耗较多计算资源,或者需要频繁发起网络/数据库请求时,每次都使用 new 关键字从头构建是非常低效的。
如果我们需要大量内容相似但细节略有不同的对象,如何优雅地解决性能与代码冗余的问题?
这就是原型模式大显身手的地方。
本文源码:https://github.com/likerhood/CodeDesignWork 其中原型模式在codedesign3.0系列
一、核心定义
原型模式(Prototype Pattern) 是一种创建型设计模式,它允许你通过复制(克隆)现有对象来生成新对象,而无需使代码依赖于它们所属的类。
其核心逻辑在于:将现有对象作为一个“原型”,通过调用该原型的克隆方法(如 Java 中的 clone())来快速生成一个包含相同状态的全新实例。
后续可以根据业务需要,对克隆出来的新实例进行局部属性的修改。
二、标准体系结构图
原型模式通常包含以下几个角色:
- 抽象原型 (Prototype): 声明一个克隆自身的接口(通常是一个
clone()方法)。 - 具体原型 (Concrete Prototype): 实现克隆接口,具体定义如何复制自己。
- 客户端 (Client): 提出创建对象的请求,通过调用原型对象的
clone()方法来获得一个新的对象。
classDiagram
class Prototype {
<<interface>>
+clone() Prototype
}
class ConcretePrototype1 {
-field1
+clone() ConcretePrototype1
}
class ConcretePrototype2 {
-field2
+clone() ConcretePrototype2
}
class Client {
-prototype: Prototype
+operation()
}
Client --> Prototype : uses
Prototype <|.. ConcretePrototype1 : implements
Prototype <|.. ConcretePrototype2 : implements
三、场景推演
在 Java 中,原型模式的实现通常依赖于 Cloneable 接口和重写 Object 类的 clone() 方法。
示例:带附件的邮件(展示深拷贝)
假设我们有一个复杂的 Email 对象,里面包含一个 Attachment (附件) 对象。
1 | // 1. 附件类 (需要被深拷贝,所以也要实现 Cloneable) |
注:除了重写 clone() 方法,在实际开发中,深拷贝也经常通过 序列化与反序列化 (Serialization) 或使用 JSON 转换工具 (如 Gson, Jackson) 来实现,这样可以避免繁琐地手动调用每一个子对象的 clone()。
四、实战案例
4.1 需求分析
假设我们需要开发一个在线考试系统。为了防止考生作弊,我们需要为每一位考生生成一份“专属试卷”:
- 试卷包含的题目内容是相同的(保证公平性)。
- 每份试卷的题目顺序必须随机打乱。
- 每道选择题的选项顺序(A/B/C/D)必须随机打乱,且对应的正确答案标识也要随之动态映射。
如果使用传统方式,面对一万名考生,系统需要从数据库读取一万次题库,并执行一万次极其繁琐的组卷和洗牌逻辑,这无疑会压垮数据库和应用服务器。
我们需要实现一个试卷生成器:
- 基础物料: 选择题(
ChoiceQuestion)、问答题(AnswerQuestion)。 - 核心动作: 输入考生姓名和学号,输出一份题目打乱、选项打乱的专属试卷。
4.2 架构图
4.2.1 面条代码架构图

痛点:客户端每次调用,Controller 都要从头创建所有题目列表,相当于每次都走一次全量构建(模拟查库),耗时极高。
4.2.2 原型模式架构图

4.3 类图对比
4.3.1 面条代码类图
classDiagram
class QuestionBankController {
+createPaper(candidate: String, number: String) String
}
class ChoiceQuestion {
-name: String
-option: Map~String, String~
-key: String
}
class AnswerQuestion {
-name: String
-key: String
}
QuestionBankController ..> ChoiceQuestion : creates
QuestionBankController ..> AnswerQuestion : creates
4.3.2 原型模式类图

4.4 时序图
4.4.1 面条代码时序图
sequenceDiagram
participant Client as ApiTest
participant Controller as QuestionBankController
Client->>Controller: createPaper("花花", "1001")
activate Controller
Controller->>Controller: new ArrayList<ChoiceQuestion>()
Controller->>Controller: new ArrayList<AnswerQuestion>()
Controller->>Controller: StringBuilder 拼接
Controller-->>Client: 返回花花试卷
deactivate Controller
Client->>Controller: createPaper("豆豆", "1002")
activate Controller
Controller->>Controller: new ArrayList<ChoiceQuestion>()
Controller->>Controller: new ArrayList<AnswerQuestion>()
Controller->>Controller: StringBuilder 拼接
Controller-->>Client: 返回豆豆试卷
deactivate Controller
4.4.2 原型模式时序图
sequenceDiagram
participant Client as ApiTest
participant Controller as QuestionBankController
participant Prototype as QuestionBank (Prototype)
participant Util as TopicRandomUtil
Note over Controller, Prototype: 构造函数中完成原型对象的初始化(只查一次库)
activate Controller
Controller->>Prototype: append(ChoiceQuestion)
Controller->>Prototype: append(AnswerQuestion)
deactivate Controller
Client->>Controller: createPaper("花花", "1001")
activate Controller
Controller->>Prototype: clone()
activate Prototype
Prototype->>Prototype: Collections.shuffle(题库)
loop 每道选择题
Prototype->>Util: random(options, key)
Util-->>Prototype: 乱序后的 Topic
end
Prototype-->>Controller: 返回克隆且乱序的新 QuestionBank
deactivate Prototype
Controller->>Controller: 设置考生姓名/学号
Controller-->>Client: 返回花花试卷
deactivate Controller
4.5 代码分析
4.5.1 面条代码(if-else/硬编码)
在 codedesign3.0-1 中,QuestionBankController 的 createPaper 方法包揽了所有的脏活累活。
1 | public class QuestionBankController { |
问题暴露: 代码极度臃肿。如果在高并发场景下,一千个考生并发请求,由于没有对象复用机制,堆内存中会瞬间产生海量的冗余对象,垃圾回收(GC)压力剧增。
4.5.2 原型模式代码
这是优雅的重构版本。首先,我们将“试卷”抽象为一个 QuestionBank 类,并实现 Cloneable 接口。
最核心的逻辑在于 clone() 方法的重写:
1 | public class QuestionBank implements Cloneable{ |
而在 QuestionBankController 中,工作量骤减:
1 | public class QuestionBankController { |
总结
通过引入原型模式,我们实现了从“每次重新构建”到“一次构建,无限克隆”的架构跃迁。这带来的直接好处是:
- 性能的大幅提升: 绕过了复杂的构造函数初始化过程,内存级别的对象复制在 JVM 中执行速度极快。
- 代码职责的解耦: Controller 不再负责繁琐的数据装配,它只需持有一个“母版”(Prototype),在需要时调用
clone即可。
工程实践注意点:
在使用原型模式时,务必警惕深拷贝与浅拷贝的陷阱。在实战代码的 QuestionBank 中,如果只执行 super.clone(),那么所有的试卷将共享同一个题目列表的内存引用。一旦某一试卷打乱了题目,其他所有试卷也会同步被打乱。因此,针对对象内部包含的 ArrayList 或 Map 等引用类型,必须手动调用它们自身的 clone() 方法来实现彻底的深拷贝。
适用场景
- 资源优化场景: 类的初始化需要消耗非常多的资源(数据、硬件资源等)。
- 性能和安全要求的场景: 通过
new产生一个对象需要非常繁琐的数据准备或访问权限,可以使用原型模式提高性能。 - 一个对象多个修改者的场景: 一个对象需要提供给其他对象访问,并且各个调用者可能都需要修改其值。可以考虑使用原型模式拷贝多个对象供调用者使用(比如在某些状态机或规则引擎中保存历史状态)。