介绍
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
浅复制
场景:我们都知道齐天大圣吹一根毫毛就可以变出一只猴子来,这只猴子跟齐天大圣长相一模一样。假定齐天大圣吹一根毫毛变出一只猴子A,这种以齐天大圣为原型,产生一只猴子A的过程就是原型模式。但是,出现了一个问题,猴子A和齐天大圣共用一根金箍棒,出现这种现象是因为这只是原型模式的浅拷贝。
代码:
1.先来描述一只猴子:
public class Monkey {
private int height;
private int weight;
private Date birthday;
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
2.描述出齐天大圣的金箍棒:
public class JinGuBang implements Serializable{
private float h=100;
private float d=10;
public void big(){
this.h *= 2;
this.d *= 2;
}
public void small(){
this.h /= 2;
this.d /= 2;
}
}
3.描述出齐天大圣:
public class QiTianDaSheng extends Monkey implements Cloneable,Serializable{
private JinGuBang jinGuBang=new JinGuBang();
public QiTianDaSheng() {
this.setBirthday(new Date());
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public JinGuBang getJinGuBang() {
return jinGuBang;
}
public void setJinGuBang(JinGuBang jinGuBang) {
this.jinGuBang = jinGuBang;
}
}
4.浅拷贝测试结果:
public class SimpleTest {
public static void main(String[] args) {
//齐天大圣出来了
QiTianDaSheng qiTianDaSheng=new QiTianDaSheng();
try {
//齐天大圣吹毫毛变出猴子A出来了
QiTianDaSheng cloneA=(QiTianDaSheng)qiTianDaSheng.clone();
//判断齐天大圣跟猴子A是不是同一个对象?答案:不是同一个,表明产生了新的对象
System.out.println(qiTianDaSheng == cloneA);
//判断齐天大圣的金箍棒跟猴子A的金箍棒是不是同一根?答案:是同一根金箍棒
//原则上来说,齐天大圣跟猴子A应该各自都有一根金箍棒,而不应该共用同一根
//说明这样的克隆是不完美的,这属于原型模式中的浅复制
//如果猴子A让金箍棒变大一倍,那么齐天大圣的金箍棒就会跟着变大一倍
System.out.println(qiTianDaSheng.getJinGuBang() == cloneA.getJinGuBang());
} catch (Exception e) {
e.printStackTrace();
}
}
}
再来看一个例子:
代码:
有一个普通对象Target1:
public class Target1 {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
再来一个普通对象Prototype1引用Target1:
public class Prototype1 implements Cloneable{
private String name;
private Target1 target1;
private List list;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public Target1 getTarget1() {
return target1;
}
public void setTarget1(Target1 target1) {
this.target1 = target1;
}
}
观察p1(Prototype1)克隆出p2的过程:
/**
* 有一个对象p1,它克隆出了对象p2
* 然后,修改p2的list的值,p1的list的值也会跟着修改,说明他们用的是同一个引用,这是属于浅复制
* 然后,修改p2的里一个引用Target1,p1里的引用Target1的值也会跟着变,这也是属于浅复制
*/
public class CloneTest1 {
public static void main(String[] args) {
//初始化p1
Prototype1 p1=new Prototype1();
p1.setName("张三");
List list=new ArrayList();
list.add("123");
p1.setList(list);
Target1 t1=new Target1();
p1.setTarget1(t1);
//根据p1复制一个p2
try {
Prototype1 p2=(Prototype1)p1.clone();
p2.getList().add("234");
p2.setName("李四");
p2.getTarget1().setName("王五");
System.out.println("p1======"+p1.getName());
System.out.println("p1======"+p1.getList());
System.out.println("p1======"+p1.getTarget1());
System.out.println("p1======"+p1.getTarget1().getName());
System.out.println("p2======"+p2.getName());
System.out.println("p2======"+p2.getList());
System.out.println("p2======"+p2.getTarget1());
System.out.println("p2======"+p2.getTarget1().getName());
} catch (Exception e) {
e.printStackTrace();
}
//执行结果:
p1======张三
p1======[123, 234]
p1======com.taofut.sjms.prototype.simple1.Target1@1b6d3586
p1======王五
p2======李四
p2======[123, 234]
p2======com.taofut.sjms.prototype.simple1.Target1@1b6d3586
p2======王五
}
}
总结:直接实现java提供的Cloneable接口而不去重写它的话,这样实现的是一种浅复制,它能实现基本类型变量的值全部复制,但是不能实现类似集合,对象引用类型的复制,它仅仅是复制了对象的引用,该引用仍然还是指向原有对象,所以实际上还是只有一个对象,这样的话,就会造成新产生的对象自身做修改,还会影响到原有对象的变化,这是不安全的,所以这个原型模式是不完美的,属于浅复制。
深复制
场景:接着上面齐天大圣的案例,如果齐天大圣吹一根毫毛变出一只猴子A后,猴子A自身也拥有自己的金箍棒,那么这样的原型模式就完美了,它成功的复制出了一个一模一样的个体,但是这两者却又是不同的对象,齐天大圣还是齐天大圣,猴子A还是猴子A,它们只是长得一模一样,这样的复制属于深复制。
代码:
1.一样的,先来一只猴子:
public class Monkey {
private int height;
private int weight;
private Date birthday;
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
2.再来一根金箍棒:
public class JinGuBang implements Serializable{
private float h=100;
private float d=10;
public void big(){
this.h *= 2;
this.d *= 2;
}
public void small(){
this.h /= 2;
this.d /= 2;
}
}
3.齐天大圣也该出来了:
public class QiTianDaSheng extends Monkey implements Cloneable,Serializable{
private JinGuBang jinGuBang=new JinGuBang();
public QiTianDaSheng() {
this.setBirthday(new Date());
}
@Override
protected Object clone() throws CloneNotSupportedException {
// return super.clone();
return deepClone();//自己重写了该方法
}
public JinGuBang getJinGuBang() {
return jinGuBang;
}
public void setJinGuBang(JinGuBang jinGuBang) {
this.jinGuBang = jinGuBang;
}
/**
* 序列化方式实现深度克隆(只是实现深度克隆的方式之一,反射也可以实现)
* 把对象的字节码数组读出来,通过字节码数组重新构造一个新的对象
* @return
*/
public Object deepClone(){
try {
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(this);
ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
QiTianDaSheng cloneB=(QiTianDaSheng)ois.readObject();
cloneB.setBirthday(new Date());
return cloneB;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
总结:以上得到的就是对象类型完全一样,但又不是同一个对象的深复制模式,其实要实现深复制模式有多种方式,这里只是列举了序列化这一种,而spring中的原型模式基本用的都是反射。