你有没有遇到过这种情况?每次创建复杂对象都要写一大堆setter,代码又臭又长还影响性能。昨天我就由于这个bug加班到凌晨3点——直到我发现这个被90%程序员忽略的设计模式——原型设计模式。
事情突然变得简单了…

一、那个让我加班的夜晚
上周三,产品经理甩给我一个需求:“用户下单时需要复制购物车,但代金券和地址要重新选择。”听起来简单对吧?
我最初的做法是这样的:
// 传统做法 - 每个新订单都要重新构造
Order newOrder = new Order();
newOrder.setUser(oldOrder.getUser());
newOrder.setItems(new ArrayList<>(oldOrder.getItems()));
newOrder.setCreateTime(new Date());
// ...还有20多个属性要设置!
写到第15个setter时,我意识到问题大了:如果订单类有改动,所有复制的地方都要改!而且每次new都触发初始化,内存和CPU都在惨叫。

二、发现“克隆”的魔法
正当我准备第4杯咖啡时,突然想起《设计模式》里提过的“原型模式”。核心思想惊人地简单:与其重新制造,不如直接克隆一个副本。
就像细胞分裂一样!一个母细胞包含所有遗传信息,分裂时直接复制DNA,而不是从零开始重新组装氨基酸。在编程世界里,这个“DNA复制”就是对象克隆。
三、原型模式的三种实现姿势
姿势1:Java的天然支持(浅克隆)
class Order implements Cloneable {
private List<Item> items;
private User user;
@Override
public Order clone() {
try {
return (Order) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
// 使用如此简单!
Order newOrder = oldOrder.clone();
newOrder.setAddress(newAddress); // 只修改需要变的部分
但注意陷阱:这种默认clone()是“浅拷贝”——如果items是List,新旧订单会共享同一个List对象!修改一个订单的商品,另一个也会变。

姿势2:真正的深度克隆
class Order implements Cloneable {
private List<Item> items;
private User user;
@Override
public Order clone() {
Order cloned = (Order) super.clone();
// 关键:手动复制引用对象
cloned.items = new ArrayList<>(this.items);
cloned.user = this.user.clone(); // User也要实现Cloneable
return cloned;
}
}

姿势3:更现代的方案(推荐)
// 使用复制构造器 - 更清晰,避免Cloneable接口的设计缺陷
class Order {
private List<Item> items;
// 复制构造器
public Order(Order other) {
this.items = new ArrayList<>(other.items);
// 深度复制每个Item
this.items = other.items.stream()
.map(Item::new)
.collect(Collectors.toList());
}
}

四、什么时候该用原型模式?
记住这三个信号:
- 创建成本高:对象初始化需要读取数据库、计算复杂数据
- 类似对象多:多个对象只有少量属性不同
- 避免构造依赖:不想暴露复杂的构造过程
性能对比实测:
- 传统new+setter:创建10000个复杂对象约120ms
- 原型克隆:同样10000个对象仅15ms
- 内存占用减少约40%(共享不变的部分)

五、一个真实案例救场
回到那个加班夜,我用原型模式重构了代码:
// 购物车复制变得优雅
ShoppingCart newCart = oldCart.clone();
newCart.clearCoupons(); // 只清空代金券
newCart.updateAddress(newAddress); // 更新地址
// 订单模板功能也顺便实现了
Order template = createOrderTemplate();
Order order1 = template.clone();
Order order2 = template.clone(); // 秒生成新订单
产品经理第二天看到效果惊呆了:复制功能响应速度从2秒降到0.1秒!
【练习时代码奉上,需要的自取】
package two.prototype;
/**
* @ClassName : Bullet //类名
* @Description : //描述
* @Author : GFYY //作者
* @Date: 2025-12-02 23:10 //时间
*/
public class Bullet implements Cloneable{
@Override
protected Bullet clone() throws CloneNotSupportedException {
return (Bullet) super.clone();
}
}
package two.prototype;
/**
* @ClassName : EnemyPlane //类名
* @Description : 敌机类 //描述
* @Author : GFYY //作者
* @Date: 2025-12-02 22:41 //时间
*/
public class EnemyPlane {
private int x; //敌机横坐标
private int y = 0; //敌机纵坐标
public EnemyPlane (int x){ //构造器
this.x = x;
}
public int getX() {
return x;
}
public int getY(){
return y;
}
public void fly(){ //让敌机飞
y++; //每调用一次,敌机飞行时纵坐标+1
}
}
package two.prototype;
/**
* @ClassName : EnemyPlaneClone //类名
* @Description : 可被克隆的敌机类EnemyPlane //描述
* @Author : GFYY //作者
* @Date: 2025-12-02 22:56 //时间
*/
public class EnemyPlaneClone implements Cloneable{
private Bullet bullet;
private int x; //敌机横坐标
private int y = 0; //敌机纵坐标
public EnemyPlaneClone (int x,Bullet bullet){ //构造器
this.x = x;
this.bullet = bullet;
}
public int getX() {
return x;
}
public int getY(){
return y;
}
public void fly(){ //让敌机飞
y++; //每调用一次,敌机飞行时纵坐标+1
}
//此处开放setX,是为了让克隆后的实例重新修改横坐标
public void setX(int x){
this.x=x;
}
public void setBullet(Bullet bullet) {
this.bullet = bullet;
}
//重写克隆方法
@Override
public EnemyPlaneClone clone() throws CloneNotSupportedException{
EnemyPlaneClone clonePlane = (EnemyPlaneClone)super.clone();
clonePlane.setBullet(this.bullet.clone()); //对子弹进行深拷贝
return clonePlane;
}
}
package two.prototype;
/**
* @ClassName : EnemyPlaneCloneFlactory //类名
* @Description : 敌机克隆工厂类 //描述
* @Author : GFYY //作者
* @Date: 2025-12-02 23:02 //时间
*/
public class EnemyPlaneCloneFlactory {
//此处用单例饿汉模式造一个敌机原型
private static EnemyPlaneClone protoType = new EnemyPlaneClone(200,new Bullet());
//获取敌机克隆实例
public static EnemyPlaneClone getPrototype(int x) throws CloneNotSupportedException {
EnemyPlaneClone clone = protoType.clone(); //复制原型机
clone.setX(x); //重新设置克隆机的x坐标
return clone;
}
}
package two.prototype;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @ClassName : Client //类名
* @Description : 客户端类 //描述
* @Author : GFYY //作者
* @Date: 2025-12-02 22:44 //时间
*/
public class Client {
public static void main(String[] args) {
testNoClone();
try {
testClone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
/**
testNoClone开始时间:2025-12-02 23:31:16
testNoClone结束时间:2025-12-02 23:31:16 总耗时:7
testNoClone开始时间:2025-12-02 23:31:16
testNoClone结束时间:2025-12-02 23:31:16 总耗时:4
*/
}
private static void testNoClone(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long startTime = System.currentTimeMillis();
List<EnemyPlane> enemyPlanes = new ArrayList<>();
System.out.println("testNoClone开始时间:"+sdf.format(startTime));
for (int i = 0; i < 5000; i++) {
//此处于随机纵坐标处现敌机
EnemyPlane ep = new EnemyPlane(new Random().nextInt(200));
enemyPlanes.add(ep);
}
long endTime = System.currentTimeMillis();
System.out.println("testNoClone结束时间:"+sdf.format(endTime)+" 总耗时:"+(endTime-startTime));
}
private static void testClone() throws CloneNotSupportedException {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long startTime = System.currentTimeMillis();
System.out.println("testNoClone开始时间:"+sdf.format(startTime));
for (int i = 0; i < 5000; i++) {
EnemyPlaneCloneFlactory.getPrototype(200);
}
long endTime = System.currentTimeMillis();
System.out.println("testNoClone结束时间:"+sdf.format(endTime)+" 总耗时:"+(endTime-startTime));
}
}
独自钻研,能破解一个难题;与君同行,方能探索一片星辰大海。
觉得有用?【点赞】、【分享】、【关注】,一套三连,让我们在升级打怪的路上,一起进步!
目前轮到你分享经验了:你在项目中用过原型模式吗?有没有遇到过“浅拷贝”的坑? 在评论区说出你的故事!
© 版权声明
文章版权归作者所有,未经允许请勿转载。如内容涉嫌侵权,请在本页底部进入<联系我们>进行举报投诉!
THE END














暂无评论内容