模式定義:
使用原型實(shí)例指定創(chuàng)建對象的種類,并且通過克隆這些原型創(chuàng)建新的對象。原型模式是一種對象創(chuàng)建型模式。
使用場景:
1传藏、在你希望創(chuàng)建一個對象的時候,不僅僅想要克隆原型實(shí)例對象的基礎(chǔ)結(jié)構(gòu),還需要他當(dāng)前的對象數(shù)據(jù)毯侦。
2哭靖、對于克隆的對象的操作并不影響原型實(shí)例對象。
以克隆層次來區(qū)分侈离,可有深克隆和淺克隆兩種试幽。
淺克隆實(shí)現(xiàn)如下:
public class PrototypeInstance implements Cloneable{
private Integer id;
private String name;
private String content;
private Date createDate;
private ArrayList<Integer> titles = new ArrayList<>();
public void addTitles(Integer value) {
this.titles.add(value);
}
// 重寫clone方法,clone來源于Object類
@Override
protected PrototypeInstance clone() {
try {
PrototypeInstance entity = (PrototypeInstance) super.clone();
entity.setName(getName());
entity.setContent(getContent());
return entity;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
// get / set method 自行補(bǔ)齊
}
@Test
public void CloneInstanceTest() {
String content = new String("當(dāng)一個種花家");
Integer id = 1;
Date date = new Date(1565515734562L);
PrototypeInstance instance = new PrototypeInstance();
instance.setId(id);
instance.setName("花花");
instance.setContent(content);
instance.setCreateDate(date);
instance.addTitles(1);
instance.addTitles(2);
PrototypeInstance cloneInstance = instance.clone();
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
}
運(yùn)行結(jié)果:
淺克隆就是拷貝一份原型實(shí)例對象的所有字段卦碾,并把字段中的引用也拷貝了铺坞。
那么它會暴露什么問題尼?
程序總要修改對象的屬性值的吧洲胖,那我們就來試試吧济榨,如下:
public class CloneTest {
@Test
public void CloneInstanceTest() {
String content = new String("當(dāng)一個種花家");
Integer id = 1;
Date date = new Date(1565515734562L);
PrototypeInstance instance = new PrototypeInstance();
instance.setId(id);
instance.setName("花花");
instance.setContent(content);
instance.setCreateDate(date);
instance.addTitles(1);
instance.addTitles(2);
PrototypeInstance cloneInstance = instance.clone();
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
// 克隆完后,我突然想修改原型實(shí)例對象的值宾濒。
System.out.println("---------- 嘗試修改原型實(shí)例對象值 -----------");
id = 2;
content = new String("一顆中國心");
date.setTime(1365215478956L);
instance.setId(id);
instance.addTitles(3);
instance.setContent(content);
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
}
}
運(yùn)行的結(jié)果:
從3 4 中看出來,怎么克隆對象的屬性值也變了屏箍?1 2 且沒有變化绘梦。
是否可以推斷一下:
Integer / String 等字段引用類型,在原型實(shí)例對象修改字段值后克隆對象并沒有產(chǎn)生變化赴魁,但是 Date / ArrayList 類型的字段在原型實(shí)例對象修改完值后克隆對象中它的字段值卻變了卸奉。
深入源碼了解下的話,你會發(fā)現(xiàn) Date / ArrayList 類型
它們都是實(shí)現(xiàn)了Cloneable接口以及使用了transient關(guān)鍵字
transient關(guān)鍵字:java語言的關(guān)鍵字颖御,變量修飾符榄棵,如果用transient聲明一個實(shí)例變量,當(dāng)對象存儲時潘拱,它的值不需要維持疹鳄。換句話來說就是,用transient關(guān)鍵字標(biāo)記的成員變量不參與序列化過程芦岂。所以在進(jìn)行對象克隆的時候瘪弓,Date / ArrayList 類型并沒有在序列化中進(jìn)行引用拷貝,僅僅是拷貝存儲值禽最,然而clone的時候 它們會進(jìn)行值的拷貝腺怯。
結(jié)論: 因?yàn)闇\度克隆對引用類型的字段進(jìn)行的是拷貝了指向?qū)ο蟮囊茫绢愋褪菍χ颠M(jìn)行備份川无。
那么如何解決這個問題尼呛占?
也就是我們接下來要說的深克隆了。
解決方式1: 就如剛才看到的源碼一樣 它們兩個是實(shí)現(xiàn)了Cloneable接口的懦趋,所以肯定是有重寫clone()方法啦晾虑。源碼里是這樣的:
所以修改原性實(shí)例對象 clone方法里面的拷貝字段就可以做到了:
運(yùn)行的結(jié)果:
完全正確,原性對象 和 克隆對象兩者互不干預(yù),不相互影響走贪,你修改你的佛猛,我寫我的
解決方式2: 直接使用序列化和反序列化來拷貝唄,代碼如下:
public class PrototypeInstance implements Cloneable,Serializable{
private Integer id;
....
public class CloneTest {
@Test
public void CloneInstanceTest() throws IOException, ClassNotFoundException {
String content = new String("當(dāng)一個種花家");
Integer id = 1;
Date date = new Date(1565515734562L);
PrototypeInstance instance = new PrototypeInstance();
instance.setId(id);
instance.setName("花花");
instance.setContent(content);
instance.setCreateDate(date);
instance.addTitles(1);
instance.addTitles(2);
// PrototypeInstance cloneInstance = instance.clone();
// 利用序列化和反序列化進(jìn)行克隆
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
PrototypeInstance cloneInstance = (PrototypeInstance) ois.readObject();
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
// 克隆完后坠狡,我突然想修改原型實(shí)例對象的值继找。
System.out.println("---------- 嘗試修改原型實(shí)例對象值 -----------");
id = 2;
content = new String("一顆中國心");
date.setTime(1365215478956L);
instance.setId(id);
instance.addTitles(3);
instance.setContent(content);
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
}
}
優(yōu)點(diǎn)方面
原型模式是在內(nèi)存二進(jìn)制流的拷貝,創(chuàng)建對象時并不會調(diào)用類的構(gòu)造方法逃沿,所以要比直接new一個對象性能好很多婴渡,如果是大量的new 對象的話 這種性能顯現(xiàn)的特別明顯。