目錄
本文的結(jié)構(gòu)如下:
- 引言
- 什么是原型模式
- 淺克隆和深克隆
- clone()
- 模式的結(jié)構(gòu)
- 典型代碼
- 代碼示例
- 優(yōu)點(diǎn)和缺點(diǎn)
- 適用環(huán)境
一、引言
我是一個(gè)很幼稚的人盈厘,所以經(jīng)常會有很幼稚的想法雅采,比如击碗,有時(shí)候上著班我就在想把跨,要是我能夠影分身多好丧没,這樣鹰椒,我可以讓一號分身陪女朋友和家人,二號分身上班敲代碼骂铁,三號分身街頭賣烤串吹零,四號分身被窩玩游戲......
我的可笑想法在當(dāng)下是不現(xiàn)實(shí)的,但在軟件開發(fā)中拉庵,卻是非常務(wù)實(shí)的灿椅。設(shè)計(jì)模式中有一個(gè)模式,可以通過一個(gè)原型對象克隆出多個(gè)一模一樣的對象,該模式稱之為原型模式茫蛹。
二操刀、什么是原型模式
在使用原型模式時(shí),我們需要首先創(chuàng)建一個(gè)原型對象婴洼,再通過復(fù)制這個(gè)原型對象來創(chuàng)建更多同類型的對象。一般這個(gè)原型對象的實(shí)例化很復(fù)雜欢唾,需要消耗很多的硬件資源或者數(shù)據(jù)資源粉捻,就像引言中說的“我”礁遣,是經(jīng)過20多年的實(shí)例化,消耗了大量的糧食才構(gòu)造成功的祟霍。
原型模式(Prototype Pattern):當(dāng)創(chuàng)建給定類的實(shí)例化過程很復(fù)雜或者代價(jià)很昂貴時(shí)盈包,可以使用原型實(shí)例指定創(chuàng)建對象的種類沸呐,并且通過拷貝這些原型創(chuàng)建新的對象。原型模式是一種對象創(chuàng)建型模式呢燥。
原型模式的工作原理很簡單:將一個(gè)原型對象傳給那個(gè)要發(fā)動(dòng)創(chuàng)建的對象,這個(gè)要發(fā)動(dòng)創(chuàng)建的對象通過請求原型對象拷貝自己來實(shí)現(xiàn)創(chuàng)建過程滥朱,拷貝通常是通過克隆方法實(shí)現(xiàn)力试。原型模式是一種“另類”的創(chuàng)建型模式,創(chuàng)建克隆對象的工廠就是原型類自身缰犁,工廠方法由克隆方法來實(shí)現(xiàn)怖糊。
三伍伤、淺克隆和深克隆
需要注意的是克隆有深克隆和淺克隆之分。
淺克侣笃颉: 在淺克隆中,如果原型對象的成員變量是值類型倦淀,將復(fù)制一份給克隆對象声畏;如果原型對象的成員變量是引用類型插龄,則將引用對象的地址復(fù)制一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內(nèi)存地址初斑。簡單來說膨处,在淺克隆中真椿,當(dāng)對象被復(fù)制時(shí)只復(fù)制它本身和其中包含的值類型的成員變量乎澄,而引用類型的成員對象并沒有復(fù)制。
深克陆馇 : 在深克隆中护盈,無論原型對象的成員變量是值類型還是引用類型羞酗,都將復(fù)制一份給克隆對象檀轨,深克隆將原型對象的所有引用對象也復(fù)制一份給克隆對象。簡單來說卫枝,在深克隆中讹挎,除了對象本身被復(fù)制外,對象所包含的所有成員變量也將復(fù)制衰伯。
四积蔚、clone()
4.1尽爆、clone()方法理解
clone()方法是Object中的一個(gè)方法,其源代碼如下:
protected native Object clone() throws CloneNotSupportedException;
可以發(fā)現(xiàn):
- 第一:Object類的clone()方法是一個(gè)native方法槐雾,native方法的效率一般來說都是遠(yuǎn)高于Java中的非native方法幅狮。這也解釋了為什么要用Object中clone()方法而不是先new一個(gè)類崇摄,然后把原始對象中的信息復(fù)制到新對象中,雖然這也實(shí)現(xiàn)了clone功能鸠儿。
JNI是Java Native Interface的 縮寫进每。從Java 1.1開始命斧,Java Native Interface(JNI)標(biāo)準(zhǔn)成為java平臺的一部分冯丙,它允許Java代碼和其他語言寫的代碼進(jìn)行交互。JNI一開始是為了本地已編譯語言泞莉,尤其是C和C++而設(shè)計(jì)的鲫趁,但是它并不妨礙你使用其他語言利虫,只要調(diào)用約定受支持就可以了。使用java與本地已編譯的代碼交互疫剃,通常會喪失平臺可移植性巢价。但是,有些情況下這樣做是可以接受的城菊,甚至是必須的凌唬,比如漏麦,使用一些舊的庫撕贞,與硬件、操作系統(tǒng)進(jìn)行交互,或者為了提高程序的性能脊奋。JNI標(biāo)準(zhǔn)至少保證本地代碼能工作在任何Java虛擬機(jī)實(shí)現(xiàn)下疙描。
- 第二:Object類中的clone()方法被protected修飾符修飾起胰。這也意味著如果要應(yīng)用clone()方法效五,必須繼承Object類,在Java中所有的類是缺省繼承Object類的脉执,也就不用關(guān)心這點(diǎn)了戒劫。然后重載clone()方法。還有一點(diǎn)要考慮的是為了讓其它類能調(diào)用這個(gè)clone類的clone()方法淘邻,重載之后要把clone()方法的屬性設(shè)置為 public湘换。
- 第三:Object.clone()方法返回一個(gè)Object對象枚尼。必須進(jìn)行強(qiáng)制類型轉(zhuǎn)換才能得到需要的類型署恍。
4.2、Java中對象的克隆
一般需要四個(gè)步驟:
- 在子類中實(shí)現(xiàn)Cloneable接口袁串。
- 為了獲取對象的一份拷貝呼巷,我們可以利用Object類的clone方法王悍。
- 在子類中覆蓋clone方法压储,聲明為public。
- 在子類的clone方法中孕似,調(diào)用super.clone()刮刑。
Cloneable接口僅僅是一個(gè)標(biāo)志接口雷绢,而且這個(gè)標(biāo)志也僅僅是針對Object類中 clone()方法的习寸,如果clone類沒有實(shí)現(xiàn)Cloneable接口,并調(diào)用了Object的clone()方法(也就是調(diào)用了super.Clone()方法)孵滞,那么Object的clone()方法就會拋出 CloneNotSupportedException 異常坊饶。
4.3、淺克隆實(shí)例
public class Student implements Cloneable{
private String name;
private int age;
private Professor professor;
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 Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", professor="
+ professor + "]";
}
public Object clone() throws CloneNotSupportedException{
return super.clone();
}
}
public class Professor {
private String name;
private int 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;
}
@Override
public String toString() {
return "Professor [name=" + name + ", age=" + age + "]";
}
}
public class ShadowCopy {
public static void main(String[] args) {
Professor p1 = new Professor();
p1.setName("Professor Zhang");
p1.setAge(30);
Student s1 = new Student();
s1.setName("xiao ming");
s1.setAge(18);
s1.setProfessor(p1);
System.out.println(s1);
try {
Student s2 = (Student) s1.clone();
Professor p2 = s2.getProfessor();
p2.setName("Professor Li");
p2.setAge(45);
s2.setProfessor(p2);
System.out.println("復(fù)制后的:s1 = " + s1);
System.out.println("復(fù)制后的:s2 = " + s2);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
測試結(jié)果會發(fā)現(xiàn)復(fù)制后打印出來的s1和s2結(jié)果是一樣的痘绎,s1和s2的導(dǎo)師都變成了45歲的Professor Li,顯然這并不是期望的結(jié)果尔苦,產(chǎn)生這個(gè)結(jié)果的原因lone()方法實(shí)現(xiàn)的是淺克隆允坚,對象引用professor只是復(fù)制了其引用蛾号,s1和s2仍是指向相同的地址塊鲜结。
4.4、深克隆實(shí)例
實(shí)現(xiàn)深克隆乐疆,可以在原clone()方法基礎(chǔ)上改進(jìn)一下,如下:
public class Student implements Cloneable{
private String name;
private int age;
private Professor professor;
public Student(String name, int age, Professor professor){
this.name = name;
this.age = age;
this.professor = professor;
}
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 Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
public Object clone(){
Student o = null;
try {
o = (Student)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
o.professor = (Professor)professor.clone();
return o;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", professor="
+ professor + "]";
}
}
public class Professor implements Cloneable{
private String name;
private int age;
public Professor(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;
}
@Override
public Object clone(){
Object o = name;
try {
o= super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return o;
}
@Override
public String toString() {
return "Professor [name=" + name + ", age=" + age + "]";
}
}
public class DeepClone {
public static void main(String[] args) {
Professor p=new Professor("wangwu",50);
Student s1=new Student("zhangsan",18, p);
System.out.println(s1);
Student s2=(Student)s1.clone();
s2.getProfessor().setName("maer");
s2.getProfessor().setAge(40);
System.out.println("復(fù)制后的:s1 = " + s1);
System.out.println("復(fù)制后的:s2 = " + s2);
}
}
也可以利用序列化反序列化來實(shí)現(xiàn)深克隆:
public class Student implements Serializable {
private String name;
private int age;
private Professor professor;
public Student(String name, int age, Professor professor){
this.name = name;
this.age = age;
this.professor = professor;
}
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 Professor getProfessor() {
return professor;
}
public void setProfessor(Professor professor) {
this.professor = professor;
}
public Object deepClone() throws IOException, ClassNotFoundException {
//將對象寫到流中
ByteArrayOutputStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//從流中讀出來
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return oi.readObject();
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", professor="
+ professor + "]";
}
}
public class Professor implements Serializable {
private String name;
private int age;
public Professor(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;
}
@Override
public String toString() {
return "Professor [name=" + name + ", age=" + age + "]";
}
}
public class DeepClone {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Professor p=new Professor("wangwu",50);
Student s1=new Student("zhangsan",18, p);
System.out.println(s1);
Student s2=(Student)s1.deepClone();
s2.getProfessor().setName("maer");
s2.getProfessor().setAge(40);
System.out.println("復(fù)制后的:s1 = " + s1);
System.out.println("復(fù)制后的:s2 = " + s2);
}
}
五儿礼、模式的結(jié)構(gòu)
介紹完淺克隆和深克隆蚊夫,再回到原型模式上懦尝。
原型模式的UML類圖如下:
在原型模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
- Prototype(抽象原型類):它是聲明克隆方法的接口,是所有具體原型類的公共父類伍绳,可以是抽象類也可以是接口冲杀,甚至還可以是具體實(shí)現(xiàn)類睹酌。
- ConcretePrototype(具體原型類):它實(shí)現(xiàn)在抽象原型類中聲明的克隆方法憋沿,在克隆方法中返回自己的一個(gè)克隆對象。
- Client(客戶類):讓一個(gè)原型對象克隆自身從而創(chuàng)建一個(gè)新的對象甥绿,在客戶類中只需要直接實(shí)例化或通過工廠方法等方式創(chuàng)建一個(gè)原型對象共缕,再通過調(diào)用該對象的克隆方法即可得到多個(gè)相同的對象士复。由于客戶類針對抽象原型類Prototype編程阱洪,因此用戶可以根據(jù)需要選擇具體原型類,系統(tǒng)具有較好的可擴(kuò)展性承璃,增加或更換具體原型類都很方便盔粹。
六程癌、典型代碼
public interface Prototype {
Prototype clone();
void setAttr(String attr);
}
public class ConcretePrototype implements Prototype {
private String attr; //成員屬性
public void setAttr(String attr) {
this.attr = attr;
}
public String getAttr() {
return this.attr;
}
public Prototype clone() {
Prototype prototype = new ConcretePrototype(); //創(chuàng)建新對象
prototype.setAttr(this.attr);
return prototype;
/*Object object = null;
try {
object = super.clone();
} catch (CloneNotSupportedException exception) {
System.err.println("Not support cloneable");
}
return (Prototype) object;*/
}
}
clone()方法是不能直接返回this的嵌莉,相信都明白。
七可婶、代碼示例
看過仙俠小說的都知道兜蠕,修煉到高境界(肯定不是練氣熊杨,金丹的渣渣了),就可以煉制身外化身桂躏,這些化身都有自己的思想剂习,可以修煉自己的功法较沪,但卻聽從主體的命令尸曼,當(dāng)然有時(shí)候化身也會叛變,這里就以仙人化身為例:
public class Immortal implements Serializable {
private String name;
private int age;
private String magicalPower;//神通
private Wife wife;//道侶
public Immortal(String name, int age, String magicalPower) {
try {
Thread.sleep(4000);//模擬仙人修煉
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name;
this.age = age;
this.magicalPower = magicalPower;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setMagicalPower(String magicalPower) {
this.magicalPower = magicalPower;
}
public void setWife(Wife wife) {
this.wife = wife;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getMagicalPower() {
return magicalPower;
}
public Wife getWife() {
return wife;
}
//使用序列化實(shí)現(xiàn)深克隆
public Immortal deepClone() throws IOException, ClassNotFoundException {
//將對象寫入流中
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//將對象從流中取出
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Immortal) ois.readObject();
}
public String toString() {
return "仙人 [姓名=" + name + ", 年齡=" + age + ", 神通="
+ magicalPower + ",道侶=" + wife.getName() + "]";
}
}
public class Wife implements Serializable{
private String name;
private int age;
public Wife(String name, int age){
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String toString(){
return "道侶 [姓名=" + name + ", 年齡=" + age + "]";
}
}
public class Client {
public static void main(String[] args) {
Wife yushiqie = new Wife("雨師妾", 3000);
Immortal immortal = new Immortal("拓拔野", 2985, "天元訣茬射,剎那芳華");
immortal.setWife(yushiqie);
System.out.println(immortal);
try {
//故事最后拓跋陪我最愛的雨師妾?dú)w隱在抛,但姑射仙子卻沒了歸宿,這里假設(shè)拓跋分出一個(gè)分身
Immortal incarnation = immortal.deepClone();
Wife guyexianzi = incarnation.getWife();
System.out.println(immortal.getWife() == incarnation.getWife()); //false
guyexianzi.setName("姑射仙子");
guyexianzi.setAge(2985);
System.out.println(incarnation);
incarnation.setWife(guyexianzi);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
八档悠、優(yōu)點(diǎn)和缺點(diǎn)
8.1、優(yōu)點(diǎn)
原型模式的主要優(yōu)點(diǎn)如下:
- 當(dāng)創(chuàng)建新的對象實(shí)例較為復(fù)雜時(shí)磨德,使用原型模式可以簡化對象的創(chuàng)建過程典挑,通過復(fù)制一個(gè)已有實(shí)例可以提高新實(shí)例的創(chuàng)建效率。
- 擴(kuò)展性較好拙寡,由于在原型模式中提供了抽象原型類琳水,在客戶端可以針對抽象原型類進(jìn)行編程在孝,而將具體原型類寫在配置文件中,增加或減少產(chǎn)品類對原有系統(tǒng)都沒有任何影響始赎。
- 原型模式提供了簡化的創(chuàng)建結(jié)構(gòu)造垛,工廠方法模式常常需要有一個(gè)與產(chǎn)品類等級結(jié)構(gòu)相同的工廠等級結(jié)構(gòu)五辽,而原型模式就不需要這樣厕隧,原型模式中產(chǎn)品的復(fù)制是通過封裝在原型類中的克隆方法實(shí)現(xiàn)的吁讨,無須專門的工廠類來創(chuàng)建產(chǎn)品。
- 可以使用深克隆的方式保存對象的狀態(tài)排龄,使用原型模式將對象復(fù)制一份并將其狀態(tài)保存起來橄维,以便在需要的時(shí)候使用(如恢復(fù)到某一歷史狀態(tài))争舞,可輔助實(shí)現(xiàn)撤銷操作澈灼。
8.2、缺點(diǎn)
原型模式的主要缺點(diǎn)如下:
- 需要為每一個(gè)類配備一個(gè)克隆方法床牧,而且該克隆方法位于一個(gè)類的內(nèi)部遭贸,當(dāng)對已有的類進(jìn)行改造時(shí)壕吹,需要修改源代碼算利,違背了“開閉原則”。
- 在實(shí)現(xiàn)深克隆時(shí)需要編寫較為復(fù)雜的代碼暂吉,而且當(dāng)對象之間存在多重的嵌套引用時(shí)慕的,為了實(shí)現(xiàn)深克隆肮街,每一層對象對應(yīng)的類都必須支持深克隆判导,實(shí)現(xiàn)起來可能會比較麻煩。
九绕辖、適用環(huán)境
在以下情況下可以考慮使用原型模式:
- 創(chuàng)建新對象成本較大(如初始化需要占用較長的時(shí)間仪际,占用太多的CPU資源或網(wǎng)絡(luò)資源)树碱,新的對象可以通過原型模式對已有對象進(jìn)行復(fù)制來獲得变秦,如果是相似對象蹦玫,則可以對其成員變量稍作修改。
- 一個(gè)對象需要提供給其他對象訪問,而且各個(gè)調(diào)用者可能都需要修改其值時(shí)歧焦,可以考慮使用原型模式拷貝多個(gè)對象供調(diào)用者使用,即保護(hù)性拷貝肚医。
- 需要避免使用分層次的工廠類來創(chuàng)建分層次的對象绢馍,并且類的實(shí)例對象只有一個(gè)或很少的幾個(gè)組合狀態(tài),通過復(fù)制原型對象得到新實(shí)例可能比使用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例更加方便肠套。