赞
踩
克隆羊问题:有一个羊,是一个类,有对应的属性,要求创建完全一样的10只羊出来。
那么实现起来很简单,我们先写出羊的类:
public class Sheep {
private String name;
private int age;
private String color;
//下面写上对应的get和set方法,以及对应的构造器
}
然后,创建10只一样的羊,就在客户端写一个代码创建:
//原始羊
Sheep sheep = new Sheep("tom",1,"白色");
//克隆羊
Sheep sheep1 = new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());
sheep1 是克隆的第一只羊,接着就可以复制十遍这个代码,然后命名不同的羊,以原始sheep为模板进行克隆。
这种方法的弊端:
如下类图所示:
其中,Prototype 是一个原型接口,在这里面把克隆自己的方法声明出来;
ConcreteProtype 可以是一系列的原型类,实现具体操作。
对于克隆羊问题,我们来利用原型设计模式进行改进:
让Sheep类,实现 Cloneable 接口:
public class Sheep implements Cloneable{ private String name; private int age; private String color; //getters&&setters&&constructors @Override protected Object clone() { Sheep sheep = null; try { sheep = (Sheep)super.clone();//使用默认Object的clone方法来完成 } catch (CloneNotSupportedException e) { System.out.println(e.getMessage()); } return sheep; } }
现在的 Sheep 类就是一个具体的原型实现类了,我们想要克隆的时候,客户端调用可以这样:
Sheep sheep1 = (Sheep) sheep.clone();
Sheep sheep2 = (Sheep) sheep.clone();
//。。。。。类似
这种做法就是原型设计模式。
(spring框架里,通过bean标签配置类的scope为prototype,就是用的原型模式)
使用上面所说的原型模式,按理说是复制出了一模一样的对象。
但我们做一个尝试,如果 sheep 类里的成员变量有一个是对象,而不是基础类型呢?
private Sheep friend;
然后我们创建、再克隆:
Sheep sheep = new Sheep("tom",1,"白色");//原始羊
sheep.setFriend(new Sheep("jack",2,"黑色"));
Sheep sheep1 = (Sheep) sheep.clone();
Sheep sheep2 = (Sheep) sheep.clone();
Sheep sheep3 = (Sheep) sheep.clone();
重写一下 Sheep 类的 toString 方法,输出信息和对应的属性的 hashcode 后会发现:
Sheep{name='tom', age=1, color='白色', friend=488970385}
Sheep{name='tom', age=1, color='白色', friend=488970385}
Sheep{name='tom', age=1, color='白色', friend=488970385}
friend 的 hashCode 值都一样,也就是克隆的类的 friend 属性其实没有被复制,而是指向了同一个对象。
这就叫浅拷贝(shallow copy):
在源码里也说明了,这个方法是shallow copy 而不是 deep copy。
在实际开发中,往往是希望克隆的过程中,如果类的成员是引用类型,也能完全克隆一份,也就是所谓的深拷贝。
深拷贝(Deep Copy):
深拷贝的实现方式,需要通过重写 clone 方法,或者通过对象的序列化。
下面来实现一下。
/* 被拷贝的类引用的类,此类的clone用默认的clone即可 */ public class CloneTarget implements Cloneable { private static final long serialVersionUID = 1L; private String cloneName; private String cloneClass; public CloneTarget(String cloneName, String cloneClass) { this.cloneName = cloneName; this.cloneClass = cloneClass; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
/* 原型类,其中有成员是引用类型,因此clone方法要重写达到深拷贝 */ public class Prototype implements Cloneable { public String name; public CloneTarget cloneTarget; public Prototype() { super(); } @Override protected Object clone() throws CloneNotSupportedException { Object o = null; //用了浅拷贝,基本数据克隆完成,但是cloneTarget指向的还是原来的对象 o = super.clone(); //单独处理引用类型 Prototype target = (Prototype) o; target.cloneTarget = (CloneTarget)cloneTarget.clone(); return target; } }
这样的话,新建一个原型Prototype的对象后,对他进行克隆,得到的里面的 CloneTarget 成员也是深拷贝的两个不一样的对象了。
但是这种方法本质上是相当于 套娃 ,因为都要单独处理重写 clone 方法,所以有些麻烦。
在 Prototype 里直接 使用序列化+反序列化,达到对这个对象整体的一个复制。
另外注意,序列化和反序列化,必须实现 Serializable 接口,所以 implements 后面不止要有 Cloneable,还有Serializable。
//利用序列化实现深拷贝 public Object deepClone(){ ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ByteArrayInputStream bis = null; ObjectInputStream ois = null; try { bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this); //反序列化 bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); Prototype copy = (Prototype) ois.readObject(); return copy; } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); }finally { try { bos.close(); oos.close(); bis.close(); ois.close(); } catch (IOException e) { e.printStackTrace(); } } return null; }
然后我们想要克隆的时候,直接调用这个 deepClone 方法就可以达到目的。
忽视掉里面的 try - catch 之类的代码,其实核心部分就是用到序列化和反序列化的总共 4 个对象。这种方法是推荐的,因为实现起来更加容易。
序列化反序列化达到深拷贝目的的原理:
在这里,我们采用的OutputStream是ByteArrayOutputStream——字节数组输出流,通过创建的ObjectOutputStream的writeObject方法,把对象写进了这个字节数组输出流。
在这里,把字节数组重新构造成一个ByteArrayInputStream——字节数组输入流,通过ObjectInputStream的readObject方法,把输入流重新构造成一个对象。
结合上面的代码再看看:
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);//写入指定的OutputStream
oos.writeObject(this);//把对象写入到输出流中,整个对象,this
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);//读取指定的InputStream
Prototype copy = (Prototype) ois.readObject();//从输入流中读取一个对象
return copy;
原型模式:
缺点:
每一个类都需要一个克隆方法,对于全新的类来说不是问题,但是如果是用已有的类进行改造,那么可能会因为要修改源代码而违背 OCP 原则。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。