1.前言
單例模式可以避免重復(fù)創(chuàng)建消耗資源的對象,但是卻不得不共用對象食铐。若是對象本身也不讓隨意訪問修改時匕垫,怎么辦?通常做法是備份到副本虐呻,其它對象操作副本象泵,最后獲取權(quán)限合并,類似git上的PR操作斟叼。
2.概念
原型模式用原型實(shí)例指定創(chuàng)建對象的種類偶惠,并通過拷貝這些原型創(chuàng)建新的對象。需要注意的關(guān)鍵字是朗涩,新的對象忽孽,類沒變。Java正好提供了Cloneable接口谢床,它標(biāo)識的類可以調(diào)用Object中實(shí)現(xiàn)的clone()方法而不拋出異常兄一,即運(yùn)行時通知虛擬機(jī)可以安全使用clone()方法返回拷貝對象。由于它直接操作內(nèi)存中的二進(jìn)制流识腿,當(dāng)大量操作或操作復(fù)雜對象時出革,性能優(yōu)勢將會很明顯。
3.場景
動物園中有一只羊渡讼,對它進(jìn)行克隆骂束,產(chǎn)生另外一只完全一樣的羊费薄,分別安排兩位有孩子的管理員照顧。有一天栖雾,對克隆羊進(jìn)行基因操作楞抡,觀察變化。
4.寫法
// 1.聲明此類可以被clone
public class Sheep implements Cloneable {
private int age;
private String sex;
private Admin admin;
public Sheep(int age, String sex, Admin admin) {
super();
this.age = age;
this.sex = sex;
this.admin = admin;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Admin getAdmin() {
return admin;
}
public void setAdmin(Admin admin) {
this.admin = admin;
}
@Override
public String toString() {
return "Sheep [age=" + age + ", sex=" + sex + ", admin=" + admin + "]";
}
// 2.調(diào)用Object的clone方法
public Sheep startClone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
}
public class Admin {
private int age;
private String sex;
private Child child;
public Admin(int age, String sex, Child child) {
super();
this.age = age;
this.sex = sex;
this.child = child;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setChild(Child child) {
this.child = child;
}
@Override
public String toString() {
return "Admin [age=" + age + ", sex=" + sex + ", child=" + child + "]";
}
}
public class Child {
}
public class Zoo {
public static void main(String[] args) {
Sheep old = new Sheep(2, "雄性", new Admin(25, "女", new Child()));
System.out.println(old.toString());
Sheep current = old.startClone();
System.out.println(current.toString());
// 對克隆羊做處理
current.setAge(1);
current.setSex("雌性");
current.getAdmin().setAge(34);
current.getAdmin().setSex("男");
System.out.println(old.toString());
System.out.println(current.toString());
}
}
根據(jù)上面的代碼析藕,我們知道羊引用了管理員召廷,管理員引用了孩子。當(dāng)對內(nèi)存中數(shù)據(jù)拷貝時账胧,除了基本數(shù)據(jù)類型(包括封裝類型)及String類型竞慢,其它的引用關(guān)系將直接傳遞給副本,并不是重新創(chuàng)建一個對象治泥。所以當(dāng)對克隆羊操作時筹煮,年齡和性別直接改變,而對管理員的操作將尋址到內(nèi)存中對應(yīng)部分進(jìn)行修改居夹,導(dǎo)致原型也被修改败潦。孩子與管理員的關(guān)系就如同管理員與羊,通過哈希值可以知道准脂,孩子始終就一個劫扒,沒有拷貝成功。
上面的錯誤是由于只拷貝了最外層對象的原因狸膏,我們稱之為淺拷貝沟饥。為了解決這個問題,需要對內(nèi)部的引用類型進(jìn)行拷貝(Java中大部分引用類型實(shí)現(xiàn)了Cloneable接口湾戳,可以方便的拷貝)贤旷,具體如下:
// 1.聲明此類可以被clone
public class Sheep implements Cloneable {
// 前面省略
// 2.調(diào)用Object的clone方法
public Sheep startClone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
// 3.調(diào)用Admin的clone方法
sheep.admin = this.admin.startClone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return sheep;
}
}
public class Admin implements Cloneable {
// 前面省略
public Admin startClone() {
Admin admin = null;
try {
admin = (Admin) super.clone();
admin.child = this.child.startClone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return admin;
}
}
public class Child implements Cloneable {
public Child startClone() {
Child child = null;
try {
child = (Child) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return child;
}
}
通過日志的打印,發(fā)現(xiàn)這種方式(深拷貝)起作用了砾脑。由1幼驶、2行可以知道,拷貝時引用類型已經(jīng)重新創(chuàng)建了對象拦止。由3县遣、4行可以知道,修改其中一個對象不會再改變另一個了汹族。
5.總結(jié)
原型模式通過Object的clone()方法實(shí)現(xiàn)萧求,由于是內(nèi)存操作,無視構(gòu)造方法和訪問權(quán)限顶瞒,直接獲取新的對象夸政。但對于引用類型,需使用深拷貝榴徐,其它淺拷貝即可守问。