什么是原型模式?它解决什么问题?
简化版
原型模式是通过复制已有对象来创建新对象,而不是每次都从零开始 new 并初始化。它主要解决复杂对象创建成本高、初始化过程重复、对象结构相似但细节不同的问题。
详细版
原型模式的核心思想是:先准备一个原型对象,需要新对象时直接复制这个原型,再按需修改少量字段。
它适合这些情况:
- 对象创建过程复杂;
- 初始化成本高;
- 对象之间大部分字段相同;
- 想避免客户端知道复杂构造细节;
- 运行时需要动态复制某类对象。
典型代码形态:
interface Prototype<T> {
T copy();
}
客户端只需要调用:
OrderTemplate template = registry.get("normal");
OrderTemplate order = template.copy();
原型模式的重点不是“复制”这个动作本身,而是用复制来复用已有对象的初始化状态。
完整版教学
一、为什么需要原型模式
普通创建对象通常是:
Report report = new Report();
report.setTitle("月报");
report.setStyle(defaultStyle);
report.setColumns(defaultColumns);
report.setPermissions(defaultPermissions);
如果这些初始化步骤很多,而且每次创建都差不多,就会出现大量重复代码。
原型模式的思路是先准备好一个标准模板:
Report prototype = new Report();
prototype.initDefaultConfig();
之后创建新报表时复制它:
Report report = prototype.copy();
report.setTitle("7 月销售月报");
这样新对象能继承原型的默认配置,只修改差异部分。
二、原型模式属于创建型模式
创建型模式关注“对象怎么创建”。
工厂模式通过工厂方法创建对象,建造者模式通过步骤组装复杂对象,原型模式则通过复制已有对象创建对象。
它的创建入口不是构造器,而是:
已有对象 -> 复制 -> 新对象
这就是原型模式和其他创建型模式最明显的差别。
三、原型模式解决的是初始化复用
很多人以为原型模式只是 clone(),其实它真正复用的是对象状态。
例如一个游戏角色模板:
等级:1
职业:法师
初始技能:火球术、闪现
默认装备:新手法杖
属性成长曲线:法师成长模板
创建新角色时,不需要重新设置这些默认值,直接复制模板,再设置玩家名称即可。
四、复制出来的对象必须是新对象
原型模式不是共享同一个对象。
正确关系是:
prototype != copy
如果返回的是原对象本身,就会导致多个使用方互相修改同一个对象。
复制后,新对象应该拥有自己的身份和生命周期。
五、原型模式的难点在深浅拷贝
如果对象字段都是基本类型或不可变对象,复制比较简单。
但如果对象里有可变引用:
class Report {
List<String> columns;
}
浅拷贝会让新旧对象共享同一个 columns 列表。一个对象修改列配置,另一个对象也会被影响。
这就是原型模式最容易出 bug 的地方。
六、常见误区与追问
原型模式的核心不是 Java clone(),而是以已有实例作为创建蓝本。若构造一个规则对象需解析500条配置,先完成1个标准原型后复制并覆盖少数字段,可以复用昂贵的初始化结果。前提是复制后对象身份独立,并且共享与独占字段都有明确约定。
| 检查维度 | 判定依据 |
|---|---|
| 创建依据 | 已有对象的状态 |
| 主要难点 | 对象图复制与业务身份处理 |
记忆钩子:原型回答“照着哪个成品再做一个”,而不是“调用哪个构造器”。
- 误区:实现 Cloneable 才算原型模式。 自定义 copy、拷贝构造器或序列化都可承载原型思想。
- 追问:为什么它属于创建型模式? 它改变对象获得方式,用复制现有实例替代从零构造。
- 误区:副本与原型字段必须完全相同。 复制后可以重置身份字段或覆盖配置,关键是初始蓝本被复用。
- 追问:复制和引用赋值有什么区别? 引用赋值没有新对象,复制至少创建独立的顶层实例。
- 追问:模式最危险的地方是什么? 未定义可变嵌套对象的共享边界,导致副本修改污染原型。
七、加强记忆
原型模式就是用“复制已有对象”来创建新对象。它适合对象初始化复杂、对象结构相似、默认配置很多的场景;真正难点不是调用复制方法,而是判断哪些字段可以共享、哪些可变对象必须深拷贝。