原型模式
原型模式(Prototype)是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象.
原型模式是一种创建型模式,运行对象再创建另一个可定制对象而无需知道创建细节.
例如,现在有个羊这样的一个类:
public class Sheep {
private String name;
private int age;
public Sheep(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
Sheep sheep = new Sheep("sh",2);
}
这个时候我们想要克隆5只sh羊,我们需要:
public static void main(String[] args) {
Sheep sheep = new Sheep("sh",2);
Sheep sheep2 = new Sheep(sheep.getName(),sheep.getAge());
Sheep sheep3 = new Sheep(sheep.getName(),sheep.getAge());
Sheep sheep4 = new Sheep(sheep.getName(),sheep.getAge());
Sheep sheep5 = new Sheep(sheep.getName(),sheep.getAge());
Sheep sheep6 = new Sheep(sheep.getName(),sheep.getAge());
}
这样的代码不仅重复冗余,而且很难拓展,此时如果我们想要在Sheep类中添加颜色这样的一个属性,我们需要大量的修改创建Sheep的构造方法,非常的麻烦.
如果用原型模式的话,需要对Sheep类进行一些改造:
package org.qinying.prototype;
//实现Cloneable接口
public class Sheep implements Cloneable {
private String name;
private int age;
public Sheep(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//覆盖Object类的clone接口
@Override
protected Object clone(){
Object clone= null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
这个时候我们再想克隆的时候,我们可以:
public static void main(String[] args) {
//创建原型
Sheep prototype = new Sheep("sh",2);
Sheep sheep2 = (Sheep) prototype.clone();
Sheep sheep3 = (Sheep) prototype.clone();
Sheep sheep4 = (Sheep) prototype.clone();
Sheep sheep5 = (Sheep) prototype.clone();
Sheep sheep6 = (Sheep) prototype.clone();
}
可以看出,此时如果我们在对Sheep类进行改造,也只需要修改原型的构造方法即可,其他的可以保持不变,提高了效率.但是这样的克隆方式是浅拷贝(在拷贝的过程中不会拷贝对象,拷贝完成之后,对象成员变量会和原成员变量指向同一片内存区域).在开发的过程中有时会使用到深拷贝.
深拷贝就是将对象的所有成员变量都拷贝一份,包括所有的值引用和对象引用.
深拷贝的实现方式:
- 通过重写克隆方法
- 通过反序列化来实现
还是拿克隆羊举例,现在我们增加一个羊的关系数组:
public class Sheep{
...
private List<Sheep> relationship = new ArrayList<>();
...
public List<Sheep> getRelationship() {
return relationship;
}
public void setRelationship(List<Sheep> relationship) {
this.relationship = relationship;
}
public void addRelation(Sheep sheep){
relationship.add(sheep);
}
@Override
public String toString() {
return "Sheep{" +
"name='" + name + '\'' +
", age=" + age +
", relationship=" + relationship +
", HashCode=" + relationship.hashCode() +
'}';
}
}
//在main方法中我们这样执行
public static void main(String[] args) {
//创建原型
Sheep prototype = new Sheep("sh",2);
Sheep prototypeFriend = new Sheep("friend",1);
Sheep prototypeFriend2 = new Sheep("friend2",3);
prototype.addRelation(prototypeFriend);
prototype.addRelation(prototypeFriend2);
Sheep sheep2 = (Sheep) prototype.clone();
System.out.println("prototype: "+prototype);
System.out.println("sheep2: "+sheep2);
System.out.println(prototype.getRelationship() == sheep2.getRelationship());
}
得到的结果如上图,在拷贝的时候只拷贝的relationship的引用值导致,克隆的对象也拥有的原来的关系信息,如果克隆对象或原对象对关系进行修改的话,两边的结果都会发生改变,这样就出现的问题.因此我们需要深拷贝.
下面开始实现深拷贝:
1.覆盖克隆方法
我们对原来的克隆方法稍作修改:
//覆盖Object类的clone接口
@Override
protected Object clone(){
Object clone= null;
try {
clone = super.clone();
Sheep sheep = (Sheep)clone;
//如果有需要克隆的对象还可以直接调用它们的clone方法
sheep.relationship = (ArrayList<Sheep>)relationship.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
结果就发生改变:
2.序列化反序列化的方式
我们让Sheep类实现Serializable接口,然后再实现一个工具类Prototypes
public class Sheep implements Cloneable, Serializable{
....
}
public class Prototypes {
/**
* 深拷贝
* @param sheep
* @return
*/
public static Sheep deepClone(Sheep sheep){
//创建输出缓存
ByteArrayOutputStream boo = null;
//创建输出流
ObjectOutputStream oos = null;
//输入缓存
ByteArrayInputStream bio = null;
//输入流
ObjectInputStream ois = null;
Sheep clonedObj = null;
try {
boo = new ByteArrayOutputStream();
oos = new ObjectOutputStream(boo);
//序列化
oos.writeObject(sheep);
bio = new ByteArrayInputStream(boo.toByteArray());
ois = new ObjectInputStream(bio);
//反序列化
clonedObj =(Sheep) ois.readObject();
}catch (Exception e){
e.printStackTrace();
}finally {
try {
boo.close();
oos.close();
bio.close();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return clonedObj;
}
}
public static void main(String[] args) {
//创建原型
Sheep prototype = new Sheep("sh",2);
Sheep prototypeFriend = new Sheep("friend",1);
Sheep prototypeFriend2 = new Sheep("friend2",3);
prototype.addRelation(prototypeFriend);
prototype.addRelation(prototypeFriend2);
Sheep sheep2 = (Sheep) prototype.clone();
System.out.println("prototype: "+prototype);
System.out.println("sheep2: "+sheep2);
System.out.println(prototype.getRelationship() == sheep2.getRelationship());
}
得到的结果如上图
可以看出ArrayList的clone方法也不是深拷贝,它得到的hashCode是一样的而我们通过序列化和反序列化的方式得到的就是真正深拷贝的值,因为hashcode不同了
不过呢,原型模式如果要使用clone方法的来实现深拷贝的话,非常依赖clone方法,如果有一个属性域没有实现克隆方法或者实现的是浅拷贝的话,都不能算是完全的深拷贝,这样的实现难度是比较大的.所以一般情况下推荐使用序列化的方式.