原型模式的核心概念“克隆”
克隆分为“浅克隆 shallow clone”、“深克隆 deep clone”
浅克隆
代码文件如下
/**
* 原型模式 - 浅克隆
* 实现两件事:
* 1. 继承接口
* 2. 重写方法
*
* 原理:只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存
*/
public class Video implements Cloneable {
private String name;
private Date dataTime;
private Video() {}
public Video(String name, Date dataTime) {
this.name = name;
this.dataTime = dataTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDataTime() {
return dataTime;
}
public void setDataTime(Date dataTime) {
this.dataTime = dataTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + ''' +
", dataTime=" + dataTime +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date date1 = new Date();
Video v1 = new Video("v1", date1);
System.err.println("v1 => " + v1);
System.err.println("v1:hashCode => " + v1.hashCode());
Video v2 = (Video) v1.clone();
v2.setName("v2");
System.err.println("v2 => " + v2);
System.err.println("v2:hashCode => " + v2.hashCode());
}
}
执行结果
v1 => Video{name='v1', dataTime=Mon May 22 17:05:54 CST 2023}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Mon May 22 17:05:54 CST 2023}
v2:hashCode => 1160460865
执行的内存指向如下
因此当重新设定date1的值,会发生克隆对象被一起改变,修改代码如下
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
// Date date1 = new Date();
// Video v1 = new Video("v1", date1);
// System.err.println("v1 => " + v1);
// System.err.println("v1:hashCode => " + v1.hashCode());
//
// Video v2 = (Video) v1.clone();
// v2.setName("v2");
// System.err.println("v2 => " + v2);
// System.err.println("v2:hashCode => " + v2.hashCode());
Date date1 = new Date();
Video v1 = new Video("v1", date1);
Video v2 = (Video) v1.clone();
v2.setName("v2");
System.err.println("v1 => " + v1);
System.err.println("v1:hashCode => " + v1.hashCode());
System.err.println("v2 => " + v2);
System.err.println("v2:hashCode => " + v2.hashCode());
System.err.println("=============");
date1.setTime(22131231);
// 此时的v1、v2的date应该不一致,但实际上被一起改变了
System.err.println("v1 => " + v1);
System.err.println("v1:hashCode => " + v1.hashCode());
System.err.println("v2 => " + v2);
System.err.println("v2:hashCode => " + v2.hashCode());
}
}
执行结果如下
v1 => Video{name='v1', dataTime=Mon May 22 17:18:11 CST 2023}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Mon May 22 17:18:11 CST 2023}
v2:hashCode => 1160460865
=============
v1 => Video{name='v1', dataTime=Thu Jan 01 14:08:51 CST 1970}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Thu Jan 01 14:08:51 CST 1970}
v2:hashCode => 1160460865
因此,浅克隆是不安全的,由此引申出"深克隆"的概念
深克隆
期望的内存指向
重点在:重写clone()
/**
* 原型模式 - 深克隆
* 实现两件事:
* 1. 继承一个接口(Cloneable) 或 继承2个接口(Cloneable、Serializable)
* 2. 重写方法
*
* 原理:复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变
*/
public class Video implements Cloneable {
private String name;
private Date dataTime;
private Video() {}
public Video(String name, Date dataTime) {
this.name = name;
this.dataTime = dataTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDataTime() {
return dataTime;
}
public void setDataTime(Date dataTime) {
this.dataTime = dataTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + ''' +
", dataTime=" + dataTime +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
Video v = (Video) obj;
// 将这个对象的属性也进行克隆,实现深克隆
v.dataTime = (Date) this.dataTime.clone();
// 还有一种方案是 继承Serializable,进行序列化/反序列化,但这会与IO进行打交道,效率上会有问题,所以不推荐
return obj;
}
}
执行结果如下
v1 => Video{name='v1', dataTime=Mon May 22 17:30:28 CST 2023}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Mon May 22 17:30:28 CST 2023}
v2:hashCode => 1160460865
=============
v1 => Video{name='v1', dataTime=Thu Jan 01 14:08:51 CST 1970}
v1:hashCode => 821270929
v2 => Video{name='v2', dataTime=Mon May 22 17:30:28 CST 2023}
v2:hashCode => 1160460865
深克隆的第二种方案 序列化/反序列化(与I/O打交道,效率有影响,不推荐)
重点在:多继承一个Serializable、重写clone()
/**
* 原型模式 - 深克隆
* 实现两件事:
* 1. 继承一个接口(Cloneable) 或 继承2个接口(Cloneable、Serializable)
* 2. 重写方法
*
* 原理:复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变
*/
public class Video implements Cloneable, Serializable {
private String name;
private Date dataTime;
private Video() {}
public Video(String name, Date dataTime) {
this.name = name;
this.dataTime = dataTime;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDataTime() {
return dataTime;
}
public void setDataTime(Date dataTime) {
this.dataTime = dataTime;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + ''' +
", dataTime=" + dataTime +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
try {
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
// 写入到当前类(也可以写入到文件)
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
执行结果如下
v1 => Video{name='v1', dataTime=Mon May 22 17:37:40 CST 2023}
v1:hashCode => 258952499
v2 => Video{name='v2', dataTime=Mon May 22 17:37:40 CST 2023}
v2:hashCode => 1637070917
=============
v1 => Video{name='v1', dataTime=Thu Jan 01 14:08:51 CST 1970}
v1:hashCode => 258952499
v2 => Video{name='v2', dataTime=Mon May 22 17:37:40 CST 2023}
v2:hashCode => 1637070917
提一嘴:
- Spring Bean使用单例模式、原型模式
- 一般原型模式+工厂模式配套使用
- new一些场景下可以使用clone代替