23天学习23种设计模式——原型模式
前言
类似于《西游记》中的孙悟空拔出猴毛,根据自己的样子变出很多猴子来。或者是《火影忍者》中鸣人使用影分身变出很多个鸣人来。设计模式中的原型模式,也是根据原型(如同孙悟空,鸣人本人就是原型)创建出新的对象。
是什么
原型模式(prototype pattern)是一种创建型模式,使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式UML类图为什么
原型模式多用于创建复杂的或构造耗时的实例,这样就能高效地创建实例对象,减轻创建对象的成本。
怎么做
在Java中,所有的类都继承自java.lang.Object
类,而该类提供了clone()
方法,这个本地方法可以将Java对象复制一份。但是需要注意的是,必须要实现标识接口Cloneable
来标识这个类可以被复制。
下面通过一个例子来实现原型模式,这是一个实现了Cloneable接口的类,并重载了Object类的clone方法。
/**
* 实现Cloneable接口表示该类可以被复制
*/
public class Sheep implements Cloneable{
private String name;
private Date birthDate;
public Sheep(String name,Date date) {
this.name = name;
this.birthDate = date;
}
public Date getBirthDate() {
return birthDate;
}
public String getName() {
return name;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public void setName(String name) {
this.name = name;
}
/**
* 重载Object类的clone方法
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
// 浅复制
return super.clone();
}
@Override
public String toString() {
return "Sheep<"+hashCode()+">'s name="+name+" birthdate:"
+birthDate.toString()+" name hashcode:"+name.hashCode()+" birthdate hashcode:"+birthDate.hashCode();
}
@Override
public boolean equals(Object obj) {
Sheep another = (Sheep) obj;
return this.name.equals(another.name)&&this.birthDate.compareTo(another.birthDate)==0;
}
}
现在我们来测试一下上面那个类对象的复制,
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Date birthDate = new Date(1341314415234L);
String name = "duoli";
Sheep duoli = new Sheep(name,birthDate);
Sheep cloneSheep = (Sheep) duoli.clone();
System.out.println(duoli.toString());
System.out.println(cloneSheep.toString());
birthDate.setTime(2124124124L);
System.out.println(duoli.toString());
System.out.println(cloneSheep.toString());
}
}
运行结果
从上面运行结果截图可以知道,原型对象和克隆对象的值是一样的,但是修改原型对象的属性之后,克隆对象的相应属性也被修改了。这是由于浅复制导致的。也就是说克隆对象只复制了原型对象的地址。当该地址对应的原型对象的值发生变化时,有着相同地址的克隆对象的属性也会发生变化。
浅复制这个问题可以通过深复制来解决,在深复制中,除了对象本身被复制外,对象所有的属性也会被复制。
深复制对于Sheep
类,我们可以进行如下的修改:
...
@Override
protected Object clone() throws CloneNotSupportedException {
//Deep Clone
Sheep sheep = (Sheep) super.clone();
sheep.birthDate = (Date) birthDate.clone();
sheep.name = name;
return sheep;
}
...
虽然,这种方式看起来比较简单,这是由于我们直接使用了引用类型Date
类已经实现好了的clone
方法。
对于我们自定义的类,往往我们要去实现Cloneable
接口,并重写Object
类的clone()
方法。
我们还可以通过序列化的方式(Serialization)来实现。通过IO流操作把对象写入流中,流中的对象就是原有对象的拷贝,不仅复制了对象本身,而且可以复制其引用的成员属性。所以,将对象写入流中,然后从流中读出来,就能实现深复制了。
下面通过将clone方法中修改为序列化操作来实现深复制:
@Override
protected Object clone() throws CloneNotSupportedException {
//Deep Clone
// Sheep sheep = (Sheep) super.clone();
// sheep.birthDate = (Date) birthDate.clone();
// sheep.a = (A) a.clone();
// sheep.name = name;
// return sheep;
try {
return deepClone();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 使用IO技术实现深复制
* @return
*/
public Sheep deepClone() throws IOException, ClassNotFoundException {
//将对象写入IO流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//将对象从IO流中取出来
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Sheep clone = (Sheep) ois.readObject();
baos.close();
oos.close();
bais.close();
ois.close();
return clone;
}
序列化深复制拷贝