1. 设计模式之原型模式:自己实现自己的对象拷贝逻辑
1.1. 介绍
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。通过已有的一个原型对象,快速生成与原型对象相同的实例。
1.2. 优缺点及建议
优点
- 性能高效:通过复制现有对象避免重复初始化,适合创建成本高的对象(如数据库连接、复杂计算实例)。
- 动态配置灵活:基于模板对象快速生成变体,仅需修改差异属性(如游戏中的敌人类型、配置模板)。
- 减少子类依赖:通过克隆替代继承,避免工厂子类膨胀(如不同风格的UI组件)。
- 隐藏构造细节:客户端无需知道对象的具体构造逻辑,直接调用
clone()
即可。
缺点
- 深拷贝实现复杂:需手动处理引用类型字段的复制,否则浅拷贝会导致对象共享(如修改克隆对象的
List
会影响原对象)。 - 破坏封装性:如果实现
Cloneable
接口并重写clone()
方法(Java中clone()
是protected
,违反面向对象设计原则)。 - 循环引用问题:若对象存在相互引用(如A→B→A),深拷贝需额外处理(如序列化或深度遍历)。
- 不适合简单对象:若对象构造成本低,直接
new
比克隆更直观(如Point(x, y)
)。
使用建议
- 资源密集型对象:如线程池、数据库连接、大型游戏角色(初始化耗时长时优先使用克隆)。
- 动态配置场景:基于模板对象生成变体(如用户权限配置、文档模板)。
- 避免工厂类爆炸:当对象变体过多时,用克隆替代工厂子类(如不同风格的按钮、图标)。
- 替代方案考虑:
- 若深拷贝太复杂,可用 复制构造器(
new User(originalUser)
)或 静态工厂方法(User.copy(original)
)。 - 对于不可变对象(如
String
),直接共享实例即可,无需克隆。
- 若深拷贝太复杂,可用 复制构造器(
不推荐场景
- 简单POJO(如
User
仅有id
和name
字段)。 - 高频创建的小对象(克隆的轻微开销可能成为瓶颈)。
- 不可变对象(如枚举、
Integer
)。
1.3. 实现及相关代码
假设有一个对象需要生成一个数据完全一样,但是是新的对象。
1.3.1. 没使用设计模式方式
接口及数据对象
public static class Component {
private String name;
public Component(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Component [name=" + name + "]";
}
}
public static class Product {
private String name;
private Component component;
public Product(String name, Component component) {
super();
this.name = name;
this.component = component;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Component getComponent() {
return component;
}
public void setComponent(Component component) {
this.component = component;
}
@Override
public String toString() {
return "Product [name=" + name + ", component=" + component + "]";
}
}
调用方法
public static void main(String[] args) {
Product product = new Product("测试产品", new Component("测试组件"));
// 手动来拷贝
Product copyProduct = new Product(product.getName(), product.getComponent());
System.out.println(copyProduct);
}
可能会遇到的问题
代码的拷贝逻辑,是每个要拷贝的调用方自己来实现的,对象复杂的话,复杂逻辑很多,造成逻辑代码复杂。
相同的拷贝逻辑会分散在很多不同的地方,如果拷贝逻辑改变了,多个调用的地方都要修改代码,可维护性、可扩展性,很差。
1.3.2. 使用设计模式方式
使用原型模式,在类里实现一个clone()
方法,自己拷贝自己。很多地方要克隆这个对象,不要自己维护克隆的逻辑,即使克隆逻辑修改了,只要在clone()
方法里面修改即可。
拷贝的时候,注意两个概念,浅拷贝
、深拷贝
。
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
定义 | 仅复制对象本身和基本类型字段,引用类型字段共享原对象的地址。 | 完全复制对象及其所有嵌套对象,引用类型字段也生成新实例。 |
内存表现 | 原对象和拷贝对象的部分字段指向同一内存地址(引用类型)。 | 原对象和拷贝对象的所有字段(包括嵌套对象)均独立占用内存。 |
修改影响 | 修改拷贝对象的引用类型字段会影响原对象。 | 修改拷贝对象的任何字段均不影响原对象。 |
实现方式 | 默认的Object.clone() 、BeanUtils.copyProperties |
手动递归复制、序列化/反序列化、工具类(如Apache Commons Lang的SerializationUtils ) |
接口及数据对象
public static class Component {
private String name;
public Component(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Component [name=" + name + "]";
}
@Override
protected Component clone() {
return new Component(getName());
}
}
public static class Product {
private String name;
private Component component;
public Product(String name, Component component) {
super();
this.name = name;
this.component = component;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Component getComponent() {
return component;
}
public void setComponent(Component component) {
this.component = component;
}
@Override
public String toString() {
return "Product [name=" + name + ", component=" + component + "]";
}
@Override
protected Product clone() {
// 浅拷贝,仅仅简单的对当前所有的变量进行一个拷贝
// return new Product(getName(), getComponent());
// 深拷贝,递归对自己引用的对象也进行拷贝
return new Product(getName(), getComponent().clone());
}
}
调用方法
public static void main(String[] args) {
Product product = new Product("测试产品", new Component("测试组件"));
// 很多地方要克隆这个对象,不要自己维护克隆的逻辑,即使克隆逻辑修改了,只要在clone()方法里面修改
Product copyProduct = product.clone();
System.out.println(copyProduct);
}