原型模式的定义
是指原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象,原型模式是复制对象的值,而不是对象本身。调用者不需要知道创建对象的细节,并且不调用构造函数,直接走字节码拷贝,属于创建型模式
适用场景
- 类初始化消耗资源较多
- new产生的一个对象需要非常繁琐的过程(包括数据准备、访问权限等)
- 构造函数较为复杂时
- 循环中需要生成大量对象时,导致可读性下降
典型的应用产品
- BeanUtils.copy()
- JSON.parseObject()
- Guava工具类库
原型模式最大的好处:简化创建对象的繁琐过程,且不需要通过构造函数创建对象
浅克隆
/**
* 原型接口,提供clone方法
*/
public interface Prototype {
/**
* 返回一个clone后的原型对象
*
* @return
*/
Prototype clone();
}
/**
* 原对象
* @author wcj
* @description
* @date 2019/8/22 10:51
*/
public class UserDto implements Prototype{
private int age;
private String name;
private String mobile;
//getter、setter方法省略
@Override
public Prototype clone() {
UserDto dto = new UserDto();
dto.setAge(this.age);
dto.setName(this.name);
dto.setMobile(this.mobile);
return dto;
}
}
/**
* 用于克隆原对象
*
* @author wcj
* @description
* @date 2019/8/22 10:53
*/
public class UserVo {
public Prototype startClone(Prototype prototype) {
return prototype.clone();
}
}
/**
* @author wcj
* @description 浅克隆测试类
* @date 2019/8/22 11:05
*/
public class ShallowCloneTest {
public static void main(String[] args) {
//原对象
UserDto userDto = new UserDto();
userDto.setAge(18);
userDto.setName("Jack");
userDto.setMobile("18010913348");
//克隆对象
UserVo vo = new UserVo();
UserDto prototype = (UserDto) vo.startClone(userDto);
System.out.println("原对象引用类型地址的值:" + userDto.getMobile());
System.out.println("克隆后对象引用类型地址的值:" + prototype.getMobile());
System.out.println("对象引用类型地址是否一致:" + (userDto.getMobile() == prototype.getMobile()));
System.out.println("对象是否一致:" + (userDto == prototype));
}
}
输出结果为:
true,false
说明克隆对象和原对象虽然是两个新对象,但是引用类型的地址是一致的,说明拷贝的不是引用的值,拷贝的是引用地址
是克隆的引用类型的地址,而不是引用类型的值,如果当原对象的值发生了改变,那么克隆对象也会随之发生改变,这两个对象就不是两个独立的对象了
深克隆
举个显而易见的例子,漩涡鸣人是一个忍者,鸣人有自己的忍具苦无,而忍者又是一个抽象的概念,有自己的属性,如果浅克隆,那么就相当于分身术后的鸣人和鸣人自己公用一个忍者,这肯定不合理,也无法战斗了,那么这种情况下,只有分身和主体都有各自的忍具才合理,这就可以理解是深克隆。代码如下
/**
* 忍者
* Created by wcj.
*/
public class Ninja {
public int height;
public int weight;
public Date birthday;
}
/**
* 忍具:苦无
* Created by wcj.
*/
public class Kunai implements Serializable {
public float h = 100;
public float d = 10;
//放大
public void big() {
this.d *= 2;
this.h *= 2;
}
//缩小
public void small() {
this.d /= 2;
this.h /= 2;
}
}
/**
* 漩涡鸣人
* Created by wcj.
*/
public class UzumakiNaruto extends Ninja implements Cloneable, Serializable {
public Kunai kunai;
public UzumakiNaruto() {
this.birthday = new Date();
this.kunai = new Kunai();
}
/**
* 这里采用深克隆,可能会出现单例被破坏的情况,所以原型模式和单例模式有点互斥的
*
* @return
*/
@Override
protected Object clone() {
return this.deepClone();
}
/**
* 字节码拷贝
*/
public Object 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);
//完整的新的对象,由readObject源码可知,是通过构造器new出来的
UzumakiNaruto copy = (UzumakiNaruto) ois.readObject();
copy.birthday = new Date();
return copy;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 浅克隆
*/
public UzumakiNaruto shallowClone(UzumakiNaruto target) {
UzumakiNaruto uzumakiNaruto = new UzumakiNaruto();
uzumakiNaruto.height = target.height;
uzumakiNaruto.weight = target.height;
uzumakiNaruto.kunai = target.kunai;
uzumakiNaruto.birthday = new Date();
return uzumakiNaruto;
}
}
/**
* 克隆测试类
*/
public class DeepCloneTest {
public static void main(String[] args) {
UzumakiNaruto uzumakiNaruto = new UzumakiNaruto();
try {
UzumakiNaruto clone = (UzumakiNaruto) uzumakiNaruto.clone();
System.out.println("测试深克隆:" + (uzumakiNaruto.kunai == clone.kunai));
} catch (Exception e) {
e.printStackTrace();
}
UzumakiNaruto q = new UzumakiNaruto();
UzumakiNaruto n = q.shallowClone(q);
System.out.println("测试浅克隆:" + (q.kunai == n.kunai));
}
}
Cloneable和Serializable都是空接口,没有定义任何方法
ArrayList中有个copy(),是深克隆还是浅克隆?
由源码可以看出,ArrayList类是实现了Cloneable的接口,并且调用Arrays的copyOf()
由Arrays类的copyOf()可看出,是创建新对象,将值拷贝到新对象中,调用的是底层的System.arraycopy()
原型模式就是如果快速构建对象方法的总结。有两种方法
- 简单工厂将getter、setter封装到某个方法中(浅克隆)
- 实现JDK提供的Cloneable接口,重写Object类的clone方法(使用字节码复制的方法),实现快速复制(深克隆)
比如,spring框架中的配置:scope="prototype"
原型模式的缺点
- 必须配备克隆或可拷贝方法
- 对克隆复杂对象或对克隆出的对象进行复杂改造时,易带来风险
- 深拷贝、浅拷贝要运用得当