原型模式定义
Specify the kinds of objects to create using a prototype instance ,and create new objects by coping this prototype
大致意思:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
原型模式:Prototype Pattern,属于创建型模式。
调用者不需要知道任何创建细节,也不用调用构造方法来创建对象。
使用场景
原型模式有如下使用场景:
- 类初始化消耗资源较多
- new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
- 构造函数比较复杂
- 循环体内生成大量对象时
- 在Spring中,原型模式应用的非常广泛,例如:scope='prototype'
我们可以将一些getter和setter之类封装成一个工厂方法,然后对于使用的人来说,调用方法就可以了,不需要知道里面的getter和setter是怎么处理的。我们也可以使用JDK提供的实现Cloneable接口,实现快速复制。
创建对象的四种方式:
new、反射、克隆、序列化
实际案例
大家是否有遇到过这种常见,就是项目中规定,不能把与数据库表映射的entity类返回给前端,所以通常返回给前端的有各种O,比如:XxxVO、XxxBO、XxxDTO...
这时候就会出现下面的场景,大家也想已经猜到了。
下面是与数据库表映射的UserEntity实体类。
public class UserEntity {
private Long id;
private String name;
private Integer age;
//....可能还有很多属性
//省略getter setter
}
1.2.3.4.5.6.7.
返回给前端或者调用方的UserVO实体类。
public class UserVO {
private Long id;
private String name;
private Integer age;
//....可能还有很多属性
//省略getter setter
}
1.2.3.4.5.6.7.
此时,从数据库里查出来的UserEntity需要转换成UserVO,然后再返回给前端(或者调用方)。
public class ObjectConvertUtil {
public static UserVo convertUserEntityToUserVO(UserEntity userEntity) {
if (userEntity == null) {
return null;
}
UserVo userVo = new UserVo();
userVo.setId(userEntity.getId());
userVo.setName(userEntity.getName());
userVo.setAge(userEntity.getAge());
//如果还有更多属性呢?
return userVo;
}
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.
从这个util类中,我们可以看出,如果一个类的属性有几十个,上百个的,这代码量是不是有点恐怖?
于是,我们通常都会使用一些工具类来处理,比如常见有以下:
BeanUtils.copy();
JSON.parseObject()
Guava工具类
.....
1.2.3.4.
这些工具类就用到了原型模式。
通过一个对象,创建一个新的对象。
也把原型模式称之为对象的拷贝、克隆。
其实对象的克隆分浅克隆和深克隆,下面我们就来聊聊浅克隆和深克隆。
- 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原来对象的属性所指向的对象的内存地址。
- 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
我们先来聊聊浅克隆,都喜欢由浅入深。
浅克隆
比如,我现在相对用户信息User进行克隆,但是User中有用户地址信息UserAddress属性。
以下是代码的实现:
//用户地址信息
public class UserAddress implements Serializable{
private String province;
private String cityCode;
public UserAddress(String province, String cityCode) {
this.province = province;
this.cityCode = cityCode;
}
}
//用户信息
public class User implements Cloneable {
private int age;
private String name;
//用户地址信息
private UserAddress userAddress;
//getter setter 省略
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
//测试
public class UserTest {
public static void main(String[] args) throws Exception {
User user = new User();
user.setAge(20);
user.setName("田维常");
UserAddress userAddress = new UserAddress("贵州", "梵净山");
user.setUserAddress(userAddress);
User clone = (User) user.clone();
System.out.println("克隆前后UserAddress比较:" + (user.getUserAddress() == clone.getUserAddress()));
}
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.
输出结果
克隆前后 UserAddress 比较:true
1.
两个对象属性 UserAddress 指向的是同一个地址。
这就是所谓的浅克隆,只是克隆了对象,对于该对象的非基本类型属性,仍指向原来对象的属性所指向的对象的内存地址。
关系如下:
深克隆
关于深克隆,我们来用一个很经典的案例,西游记里的孙悟空。一个孙悟空能变成n多个孙悟空,手里都会拿着一个金箍棒。
按照前面的浅克隆,结果就是:孙悟空倒是变成很多孙悟空,但是金箍棒用的是同一根。
深克隆的结果是:孙悟空变成了很多个,金箍棒也变成很多个根。
下面我们用代码来实现:
//猴子,有身高体重和生日
public class Monkey {
public int height;
public int weight;
public Date birthday;
}
1.2.3.4.5.6.
孙悟空也是猴子,兵器 孙悟空有个金箍棒:
import java.io.Serializable;
//孙悟空的金箍棒
public class JinGuBang implements Serializable{
public float h=100;
public float d=10;
//金箍棒变大
public void big(){
this.h *=10;
this.d *=10;
}
//金箍棒变小
public void small(){
this.h /=10;
this.d /=10;
}
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.
齐天大圣孙悟空:
import java.io.*;
import java.util.Date;
//孙悟空有七十二变,拔猴毛生成一个金箍棒
//使用JDK的克隆机制,
//实现Cloneable并重写clone方法
public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {
public JinGuBang jinGuBang;
public QiTianDaSheng() {
this.birthday = new Date();
this.jinGuBang = new JinGuBang();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return this.deepClone();
}
//深克隆
public QiTianDaSheng deepClone() {
try {
//内存中操作完成、对象读写,是通过字节码直接操作
//与序列化操作类似
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream bis = new ObjectInputStream(bais);
//完成一个新的对象,底层是使用new创建的一个对象
//详情可以了解readObject方法
QiTianDaSheng qiTianDaSheng = (QiTianDaSheng) bis.readObject();
//每个猴子的生日不一样,所以每次拷贝的时候,把生日改一下
qiTianDaSheng.birthday = new Date();
return qiTianDaSheng;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
//浅克隆,就是简单的赋值
public QiTianDaSheng shalllowClone(QiTianDaSheng target) {
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
qiTianDaSheng.height = target.height;
qiTianDaSheng.weight = target.weight;
qiTianDaSheng.jinGuBang = target.jinGuBang;
qiTianDaSheng.birthday = new Date();
return qiTianDaSheng;
}
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.
接着我们就来测试一下:
public class DeepCloneTest {
public static void main(String[] args) {
QiTianDaSheng qiTianDaSheng = new QiTianDaSheng();
try {
QiTianDaSheng newObject = (QiTianDaSheng) qiTianDaSheng.clone();
System.out.print("深克隆后 ");
System.out.println("金箍棒是否一直:" + (qiTianDaSheng.jinGuBang == newObject.jinGuBang));
} catch (Exception ex) {
ex.printStackTrace();
}
QiTianDaSheng newObject=qiTianDaSheng.shalllowClone(qiTianDaSheng);
System.out.print("浅克隆后 ");
System.out.println("金箍棒是否一直:" + (qiTianDaSheng.jinGuBang == newObject.jinGuBang));
}
}
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.
输出结果为:
深克隆后 金箍棒是否一直:false
浅克隆后 金箍棒是否一直:true
1.2.3.
结论
深克隆后每个孙悟空都有自己的金箍棒,而浅克隆后每个孙悟空用的金箍棒实质上还是同一根。
总结
切记:深和浅,指的是克隆对象里的属性(引用类型)是否指向同一个内存地址。
为了更深刻的理解深克隆和浅克隆,我们回答文中的简历拷贝的故事。
- 深拷贝:拷贝一份简历,然后对简历中的信息进行修改成自己的
- 浅拷贝:拷贝一份简历,简历内容完全不变
优点:
- Java 原型模式基于内存二进制流复制,比直接 new 的性能会更好一些。
- 可以利用深克隆保存对象状态,存一份旧的(克隆出来),在对其修改,可以充当一个撤销功能。
缺点:
- 需要配置 clone 方法,改造时需要对已有类进行修改,违背 “开闭原则”。
- 如果对象间存在多重嵌套引用时,每一层都需要实现克隆。