创建型模式-原型模式
原型模式(Prototype)
原型模式的特点是复制,通过一个现有的对象实例复制出一个新的对象实例。
简单案例-浅克隆
假设现在有一个游戏角色,想要根据这个角色再复制一个,然后再把复制的角色改一改变成一个新的角色。
Java中实现简单的原型模式:
- 类需要实现Cloneable接口
- 重写clone方法
游戏角色
public class GameCharacter implements Cloneable{
private int age;
private String name;
private Date createTime;
@Override
public GameCharacter clone() {
try {
return (GameCharacter) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
//省略get、set方法以及构造方法
@Override
public String toString() {
return "GameCharacter{" +
"age=" + age +
", name='" + name + '\'' +
", createTime=" + createTime +
'}';
}
}
使用
public class Developer {
public static void main(String[] args) {
GameCharacter v1 = new GameCharacter(25, "鲁班", new Date());
GameCharacter v2 = v1.clone();
System.out.println("原对象:"+v1);
System.out.println("克隆对象:"+v2);
}
}
输出结果:
到这里就完成了一个简单的原型模式,但是这个案例存在问题,假设我现在想要修改克隆对象的3个属性,如下:
public class Developer {
public static void main(String[] args) {
Date date = new Date();
GameCharacter v1 = new GameCharacter(25, "鲁班", date);
GameCharacter v2 = (GameCharacter) v1.clone();
System.out.println("原对象:"+v1);
System.out.println("克隆对象:"+v2);
System.out.println("===================================================");
//修改v2的属性
v2.setAge(30);
v2.setName("马可");
v2.getCreateTime().setTime(1680156978);
System.out.println("原对象:"+v1);
System.out.println("克隆对象:"+v2);
}
}
这里的输出结果中就可以看到我修改的是date的时间,并设置给克隆对象,但是原对象的createTime也修改了,这种情况就称为浅克隆。
浅克隆:如果是基本类型的值会复制给另一个对象属性,如果是引用对象类型让克隆对象的属性指向引用类型的地址
这里特别说明String一种特殊的引用类型,String内部使用final修饰的数组,是不可变的,如果对String重新设置值指向的就不是同一个地址了。
所以案例对String设置值是不会影响原对象的。
以案例图解:
在对象属性内部没有引用类型对象时可以使用浅克隆达到复制的目的,且不会影响原有对象。
如果对象内部有引用类型对象也想不影响原有对象,怎么处理?这里就引出了浅克隆对应的另一种克隆方式:深克隆。
进阶-深克隆
深克隆有两种实现方式:
- 对引用类型单独克隆
- 使用序列化和反序列化方式
对引用类型单独克隆
修改对象的clone方法,单独对createTime属性克隆,如果是其他引用类型,该引用对象类型需要实现Cloneable接口。
public GameCharacter clone() {
try {
GameCharacter obj = (GameCharacter) super.clone();
obj.createTime = (Date) this.createTime.clone();
return obj;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
贴一个执行成功的截图:
序列化和反序列化
一共两步:
- 对象实现Serializable接口
- 修改clone方法实现
public class GameCharacter implements Cloneable,Serializable{
private int age;
private String name;
private Date createTime;
@Override
public GameCharacter clone() {
GameCharacter gameCharacter = null;
//先将当前对象转为字节流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
//然后再反序列化生成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
try {
ObjectInputStream ois = new ObjectInputStream(bais);
gameCharacter = (GameCharacter) ois.readObject();
ois.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return gameCharacter;
}
//省略get、set方法以及构造方法
}
对于一些较少的引用类型,序列化和反序列的效率相对于单个克隆引用类型方式来说,效率比较低。
总结一下,原型模式的实现:
-
浅克隆:适用于对象内部没有引用类型
- 实现Cloneable接口
- 重写clone方法
-
深克隆:解决浅克隆复制对象内引用类型的问题
- 单独对引用类型克隆
- 使用序列化和反序列化方式
原型模式提供了一种创建复杂对象时的简单方法,原型模式更多是用于创建复杂的或者耗时的对象实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效。