1 原型模式的简单介绍
原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式主要适用于以下场景:
1、类初始化消耗资源较多;
2、new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等);
3、构造函数比较复杂;
4、循环体中生产大量对象时。
典型的原型应用有BeanUtils.copy(),JSON.parseObject,JDK中的Cloneable等。
原型模式在拷贝对象时又分为浅克隆与深克隆,下面就举个简单的例子来分析一下。
假设现在的业务是要批量插入一个班的学生数据,在学生数据中,班主任、班级、年级、学校等是一样的,那么在循环的封装学生数据的时候可以使用克隆来减少一些工作量。
2 浅克隆
一个标准的原型模式代码,应该是需要一个定义了克隆方法的接口的(类似下方代码这样的)。
public interface Prototype {
Prototype clone();
}
在java里面,所有的class类都继承Object类,在Object类有个clone()方法,它被定义成只能在子类中调用,所以如果我们想要在外部的类中调用clone()方法需要将其重写,但是光重写是不够的,在调用的时候会报CloneNotSupportedException异常,我们查看Object的源码注释就可以知道
/**
* .....
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
所以最终我们创建的具体需要克隆的对象StudentPrototype是下面这样子的(这里为了演示方便,用了lombok插件):
@Data
public class StudentPrototype implements Cloneable {
/** 学生id */
private Integer studentId;
/** 学生名称 */
private String studentName;
/** 生日 */
private Date birthday;
/** 班级id */
private Integer classId;
/** 年级id */
private Integer gradeId;
/** 学校id */
private Integer schoolId;
/** 家长信息 */
private ParentInfo parentInfo;
@Override
protected StudentPrototype clone() throws CloneNotSupportedException {
return (StudentPrototype)super.clone();
}
}
再创建一个家长类ParentInfo:
@Getter
@Setter
public class ParentInfo {
private String name;
private String phone;
}
测试代码:
public class PrototypeTest {
public static void main(String[] args) {
StudentPrototype sp = new StudentPrototype();
sp.setStudentId(111);
sp.setStudentName("钢铁侠");
sp.setBirthday(new Date());
sp.setClassId(222);
sp.setGradeId(333);
sp.setSchoolId(444);
ParentInfo parentInfo = new ParentInfo();
parentInfo.setName("绿巨人");
parentInfo.setPhone("13012345678");
sp.setParentInfo(parentInfo);
try {
StudentPrototype spClone = sp.clone();
System.out.println("原对象====>"+sp);
System.out.println("克隆对象====>"+spClone);
System.out.println("原对象引用对象地址值====>"+sp.getParentInfo());
System.out.println("克隆对象引用对象地址值====>"+spClone.getParentInfo());
System.out.println("对象地址比较:" + (sp.getParentInfo() == spClone.getParentInfo()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
输出结果:
从测试结果看出parentInfo 的引用地址是相同的,意味着复制的不是值,而是引用的地址。这样的话, 如果我们修改任意一个对象中的属性值, sp和 spCone 的parentInfo 值都会改变,意味着在修改了一个学生的家长名字叫“张三”,那所有的学生家长名字就都叫张三。这就是我们常说的浅克隆。只是完整复制了值类型数据,没有赋值引用对象。换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。下面我们来看深度克隆继续改造。
2 深度克隆
浅克隆中的对象引用是一样的,其内的数据属于公共变量,那根据我们的需求,我们希望克隆出来的对象都是独立的个体,那我们就需要使每个克隆出来的对象内部引用的对象都是独立的,对于上面的代码,我们有几种方式修改。
2.1 重写clone()
@Override
protected StudentPrototype clone() throws CloneNotSupportedException {
StudentPrototype clone = (StudentPrototype) super.clone();
clone.setParentInfo(new ParentInfo());
return clone;
}
这里就是手动的将内部引用的对象进行处理,我们常用的ArrayList 就实现了Cloneable 接口,来看代码clone()方法的实现:
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
2.2 序列化与反序列化
在上篇叫单例模式的文章中也有介绍到,对象是可以通过反序列化创建的,并且创建出来的对象及其内部的引用和原来的不一样,会重新开辟内存。
要先实现序列化接口,自身及其内的类都要实现序列化接口
加一个深度克隆方法:
public StudentPrototype deepClone(){
try{
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (StudentPrototype)ois.readObject();
}catch (Exception e){
e.printStackTrace();
return null;
}
}
测试:
public static void main(String[] args) {
StudentPrototype sp = new StudentPrototype();
sp.setStudentId(111);
sp.setStudentName("钢铁侠");
sp.setBirthday(new Date());
sp.setClassId(222);
sp.setGradeId(333);
sp.setSchoolId(444);
ParentInfo parentInfo = new ParentInfo();
parentInfo.setName("绿巨人");
parentInfo.setPhone("13012345678");
sp.setParentInfo(parentInfo);
StudentPrototype spClone = sp.deepClone();
System.out.println("原对象====>" + sp);
System.out.println("克隆对象====>" + spClone);
System.out.println("原对象引用对象地址值====>" + sp.getParentInfo());
System.out.println("克隆对象引用对象地址值====>" + spClone.getParentInfo());
System.out.println("对象地址比较:" + (sp.getParentInfo() == spClone.getParentInfo()));
}
输出结果: