原型模式
原型模式解决的是大量属性复制问题。
本文将介绍几种深度属性复制方式、几种浅度属性复制方式。
本文源码地址将在文末给出。
未使用原型模式之前的代码
public static void testCloneStupid() {
PrototypePerson person = new PrototypePerson()
.setId("10001")
.setAge(18)
.setName("初见")
.setHobbies(CollUtil.newArrayList("吃奶", "睡觉"));
// 手动复制属性
PrototypePerson clonePerson = new PrototypePerson();
clonePerson.setName(person.getName());
clonePerson.setAge(person.getAge());
clonePerson.setId(person.getId());
// .... 还有很多属性需要复制
clonePerson.setHobbies(person.getHobbies());
clonePerson.getHobbies().add("打豆豆");
System.out.println("person:" + person.toString());
System.out.println("clonePerson:" + clonePerson.toString());
}
代码规范工整,有什么问题 ?大家想象一下,假如有100+个属性需要赋值,怎么办?原型模式解决的就是这样大量属性复制引起的问题。
原型模式改造后的代码
public class PrototypePerson implements Cloneable, Serializable {
private String id;
private String name;
private Integer age;
private List<String> hobbies;
@Override
public PrototypePerson clone() {
try {
// jdk自带的clone必须实现 Cloneable, Serializable 两个接口
return (PrototypePerson) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
调用者使用代码
public static void testJdkClone() {
PrototypePerson person = new PrototypePerson()
.setId("10001")
.setAge(18)
.setName("初见")
.setHobbies(CollUtil.newArrayList("吃奶", "睡觉"));
// jdk自带的克隆
PrototypePerson clonePerson = person.clone();
clonePerson.getHobbies().add("打豆豆");
System.out.println("person:" + person.toString());
System.out.println("clonePerson:" + clonePerson.toString());
}
输出如下:
使用原型模式,大大简化了调用者的代码。将实现细节写到了实体类里面。
但是问题来了,复制对类型的属性时,复制的是引用,所以修改person
的 hobbies 时会同步修改 clonePerson 的属性。
深度复制与浅复制
浅复制:
当复制对象类型的属性时,复制的是对象的引用(像jdk的clone是浅复制)。
深复制:
完全复制源对象的属性,包括对象类型的属性。
深度复制 之 字节流序列化方式
public static <T> T deepClone(T obj) {
if (null == obj) return null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Object o = ois.readObject();
return (T) o;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
调用代码:
public static void testDeepClone() {
PrototypePerson person = new PrototypePerson()
.setId("10001")
.setAge(18)
.setName("初见")
.setHobbies(CollUtil.newArrayList("吃奶", "睡觉"));
PrototypePerson clonePerson = PrototypeUtils.deepClone(person);
clonePerson.getHobbies().add("打豆豆");
System.out.println("person:" + person.toString());
System.out.println("clonePerson:" + clonePerson.toString());
}
输出结果:
字节流序列化深度复制
方式常用,性能高。
深度复制 之 Json序列化方式
public static <T> T deepCloneByJson(T obj) {
String jsonString = JSON.toJSONString(obj);
Object object = JSON.parseObject(jsonString, obj.getClass());
return (T) object;
}
Json序列化深度复制
方式编码简单,小白也能记住 ~
这里就不给测试代码了,输出结果同上。
浅复制 之 BeanUtils
// 这里使用的是 Spring的BeanUtils
// import org.springframework.beans.BeanUtils;
public static void testBeanUtilsClone() {
PrototypePerson person = new PrototypePerson()
.setId("10001")
.setAge(18)
.setName("初见")
.setHobbies(CollUtil.newArrayList("吃奶", "睡觉"));
PrototypePerson clonePerson = new PrototypePerson();
// 借助BeanUtils辅助属性
BeanUtils.copyProperties(person, clonePerson);
clonePerson.getHobbies().add("打豆豆");
System.out.println("person:" + person.toString());
System.out.println("clonePerson:" + clonePerson.toString());
}
浅复制 之 jdk Object.clone() 方式
public class PrototypePerson implements Cloneable, Serializable {
private String id;
// ....
@Override
public PrototypePerson clone() {
try {
// 使用Jdk的浅复制
return (PrototypePerson) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
特别交代
- 并非所有原型模式都需要深度复制,看实际的业务场景。
- 原型模式的本意是较少手动复制大量属性的愚蠢操作。
- 标准的原型模式需要对象实现 Cloneable, Serializable 两个接口。
- 建议的方式:对象实现 Serializable 接口(不用实现Cloneable接口),提供克隆工具类: PrototypeUtils,调用者使用PrototypeUtils进行属性复制,由决定浅复制还是深复制。
PrototypeUtils工具类代码
import com.alibaba.fastjson.JSON;
import org.springframework.beans.BeanUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* @description: 原型模式工具类
* @author: chujian
* @since: 2022-02-19 16:23
**/
@SuppressWarnings("unchecked")
public class PrototypeUtils {
/**
* 浅克隆
*
* @param obj
* @param <T>
* @return
*/
public static <T> T clone(T obj) {
if (null == obj) return null;
Object newInstance = null;
try {
newInstance = obj.getClass().getConstructor().newInstance();
BeanUtils.copyProperties(obj, newInstance);
return (T) newInstance;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 深度克隆
*
* @param obj
* @param <T>
* @return
*/
public static <T> T deepClone(T obj) {
if (null == obj) return null;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
Object o = ois.readObject();
return (T) o;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 深度克隆,通过Json序列化的方式
*
* @param obj
* @param <T>
* @return
*/
public static <T> T deepCloneByJson(T obj) {
String jsonString = JSON.toJSONString(obj);
Object object = JSON.parseObject(jsonString, obj.getClass());
return (T) object;
}
}
本文完整源码地址
码云代码地址