【卻說那妖精與大聖鬥經半日,不分勝敗。行者把棒丟起,叫一聲“變!”就以一變十,以十變百,以百變千,半天里,好似蛇游蟒攪,亂打妖邪。妖邪慌了手腳,將身一閃,化道清風,即奔碧空之上逃走。行者念聲咒語,將鐵棒收做一根,縱祥光一直趕來。】 在西游記第九十五回【假合真形擒玉兔 真陰歸正會靈元】中,孫行者“毆打 ...
【卻說那妖精與大聖鬥經半日,不分勝敗。行者把棒丟起,叫一聲“變!”就以一變十,以十變百,以百變千,半天里,好似蛇游蟒攪,亂打妖邪。妖邪慌了手腳,將身一閃,化道清風,即奔碧空之上逃走。行者念聲咒語,將鐵棒收做一根,縱祥光一直趕來。】
在西游記第九十五回【假合真形擒玉兔 真陰歸正會靈元】中,孫行者“毆打”玉兔精的時候,將如意金箍棒從一根化作了千百根,打得玉兔精無從招架。
這千百根金箍棒的屬性應該是一樣的,如果孫悟空每次都要新建一個新的金箍棒對象,然後把原有的金箍棒的屬性複製過去,如此重覆千百次,未免太過麻煩,所以我們這裡假設孫悟空使用了原型模式來創建多個相同屬性的金箍棒實例。
在詳細介紹原型模式之前,我們需要先瞭解一下java.lang.Object#clone()方法以及java.lang.Cloneable介面的功能及實現:
java.lang.Cloneable
/** * A class implements the <code>Cloneable</code> interface to * indicate to the {@link java.lang.Object#clone()} method that it * is legal for that method to make a * field-for-field copy of instances of that class. * <p> * Invoking Object's clone method on an instance that does not implement the * <code>Cloneable</code> interface results in the exception * <code>CloneNotSupportedException</code> being thrown. * <p> * By convention, classes that implement this interface should override * <tt>Object.clone</tt> (which is protected) with a public method. * See {@link java.lang.Object#clone()} for details on overriding this * method. * <p> * Note that this interface does <i>not</i> contain the <tt>clone</tt> method. * Therefore, it is not possible to clone an object merely by virtue of the * fact that it implements this interface. Even if the clone method is invoked * reflectively, there is no guarantee that it will succeed. * * @author unascribed * @see java.lang.CloneNotSupportedException * @see java.lang.Object#clone() * @since JDK1.0 */ public interface Cloneable { }
Java doc的意思大致是說:一個類實現了Cloneable介面,就是在運行時向虛擬機表明當前類可以合法地使用Object類的clone()方法,來進行對象內容的拷貝。假設沒有實現Cloneable介面就調用clone()方法的話,雖然能夠通過編譯,但是會在運行時拋出java.lang.CloneNotSupportedException。一般來說,實現Cloneable介面的類需要重寫Object類的protected方法,並且聲明重寫方法為public的。需要註意的是,Cloneable介面並不包含clone()方法。因此,一個類僅僅實現Cloneable介面就想成功實現clone()功能是不可能的。即使反射調用也不保證會成功。
也就是說,要想調用源生的Object類的clone()方法,我們必須讓原型類實現Cloneable介面。那麼,Object類的clone()的優勢在哪裡呢?
/** * Creates and returns a copy of this object. The precise meaning * of "copy" may depend on the class of the object. The general * intent is that, for any object {@code x}, the expression: * <blockquote> * <pre> * x.clone() != x</pre></blockquote> * will be true, and that the expression: * <blockquote> * <pre> * x.clone().getClass() == x.getClass()</pre></blockquote> * will be {@code true}, but these are not absolute requirements. * While it is typically the case that: * <blockquote> * <pre> * x.clone().equals(x)</pre></blockquote> * will be {@code true}, this is not an absolute requirement. * <p> * By convention, the returned object should be obtained by calling * {@code super.clone}. If a class and all of its superclasses (except * {@code Object}) obey this convention, it will be the case that * {@code x.clone().getClass() == x.getClass()}. * <p> * By convention, the object returned by this method should be independent * of this object (which is being cloned). To achieve this independence, * it may be necessary to modify one or more fields of the object returned * by {@code super.clone} before returning it. Typically, this means * copying any mutable objects that comprise the internal "deep structure" * of the object being cloned and replacing the references to these * objects with references to the copies. If a class contains only * primitive fields or references to immutable objects, then it is usually * the case that no fields in the object returned by {@code super.clone} * need to be modified. * <p> * The method {@code clone} for class {@code Object} performs a * specific cloning operation. First, if the class of this object does * not implement the interface {@code Cloneable}, then a * {@code CloneNotSupportedException} is thrown. Note that all arrays * are considered to implement the interface {@code Cloneable} and that * the return type of the {@code clone} method of an array type {@code T[]} * is {@code T[]} where T is any reference or primitive type. * Otherwise, this method creates a new instance of the class of this * object and initializes all its fields with exactly the contents of * the corresponding fields of this object, as if by assignment; the * contents of the fields are not themselves cloned. Thus, this method * performs a "shallow copy" of this object, not a "deep copy" operation. * <p> * The class {@code Object} does not itself implement the interface * {@code Cloneable}, so calling the {@code clone} method on an object * whose class is {@code Object} will result in throwing an * exception at run time. * * @return a clone of this instance. * @throws CloneNotSupportedException if the object's class does not * support the {@code Cloneable} interface. Subclasses * that override the {@code clone} method can also * throw this exception to indicate that an instance cannot * be cloned. * @see java.lang.Cloneable */ protected native Object clone() throws CloneNotSupportedException;
我們看到clone()方法是一個native方法,native方法的效率一般遠高於非native方法。同時我們也可以看到關於clone()方法的描述也印證了Cloneable介面的相關介紹,如protected以及CloneNotSupportedException等。
關於clone()方法的表現如下:
x.clone() !=x;
x.clone().getClass() == x.getClass();
x.clone().equals(x) == true;
這裡還要介紹關於深複製與淺複製的概念:
淺複製對象的所有屬性都與原對象具有相同的值,包括引用其他對象的變數,對這些對象的引用依然指向原來的對象。
而深複製對象會將原對象的所有屬性都複製一遍,包括原對象引用的對象,深複製會複製新的引用對象作為自己的變數而不使用原來的對象。
淺複製原型模式
package com.tirion.design.prototype; public class GoldenCudgel implements Cloneable { public GoldenCudgel() { } public GoldenCudgel(boolean disappear) { this.disappear = disappear; } private boolean disappear; public boolean isDisappear() { return disappear; } public void setDisappear(boolean disappear) { this.disappear = disappear; } public GoldenCudgel clone() { GoldenCudgel goldenCudgel = null; try { goldenCudgel = (GoldenCudgel) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return goldenCudgel; } public boolean equals(GoldenCudgel obj) { return obj.isDisappear() == disappear; } }
悟空
package com.tirion.design.prototype; public class WuKong { private static GoldenCudgel goldenCudgel = new GoldenCudgel(false); public static void main(String[] args) { GoldenCudgel copyGoldenCudgel = goldenCudgel.clone(); System.out.println(goldenCudgel); System.out.println(copyGoldenCudgel); System.out.println(goldenCudgel != copyGoldenCudgel); System.out.println(goldenCudgel.getClass() == copyGoldenCudgel.getClass()); System.out.println(goldenCudgel.equals(copyGoldenCudgel)); } }
列印結果
com.tirion.design.prototype.GoldenCudgel@74a14482 com.tirion.design.prototype.GoldenCudgel@1540e19d true true true
在金箍棒GoldenCudgel中,我們不僅提供了clone()方法的實現,還重寫了queals()方法用於檢驗複製結果。
我們可以看到,孫悟空在創建新的金箍棒對象時,調用自身持有的金箍棒的clone()方法,就得到了一個新的金箍棒對象,它的屬性值disappear(是否消失)的值在複製後保持不變(如果金箍棒有其他更多屬性,也會保持不變,這裡我們不過多贅述)。
如果孫悟空要複製一千根金箍棒,那麼他就調用一千次自身持有的金箍棒的clone()方法即可。
通過原型模式,我們可以通過調用原型複製方法,不需要手動設置屬性,就可以達到產生與原對象相同屬性的對象的目的,大大簡化了我們創建原型對象的工作量。
值得註意的是,原型模式是一種對象的創建模式,它並沒有要求必須要通過Cloneable介面來完成,當你為一個類提供一個複製自身的方法,所有要創建相同屬性的該類對象的使用者,都通過該方法來創建新對象,那麼也是使用了原型模式,只是沒有實現Cloneable方便安全而已。
深複製原型模式
我們都知道,孫悟空除了著名的筋斗雲、火眼金睛和七十二變之外,還有很多其他的法術,比如身外身法術,就是產生一個自身的複製,下麵我們來看孫悟空的深複製與淺複製的區別。
新的悟空對象,提供了淺複製與深複製兩個複製方法
package com.tirion.design.prototype; public class WuKong implements Cloneable { private GoldenCudgel goldenCudgel; public GoldenCudgel getGoldenCudgel() { return goldenCudgel; } public void setGoldenCudgel(GoldenCudgel goldenCudgel) { this.goldenCudgel = goldenCudgel; } public WuKong() { goldenCudgel = new GoldenCudgel(false); } public WuKong clone() { WuKong wuKong = null; try { wuKong = (WuKong) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return wuKong; } public WuKong deepClone() { WuKong wuKong = null; try { wuKong = (WuKong) super.clone(); wuKong.setGoldenCudgel(wuKong.getGoldenCudgel().clone()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return wuKong; } public static void main(String[] args) { WuKong wuKong = new WuKong(); WuKong wuKongCopy = wuKong.clone(); System.out.println("淺複製後悟空是否為同一個對象" + (wuKong == wuKongCopy)); System.out.println("淺複製後金箍棒是否為同一個對象" + (wuKong.getGoldenCudgel() == wuKongCopy.getGoldenCudgel())); System.out.println("淺複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear()); System.out.println("淺複製對象金箍棒屬性發生改變..."); wuKongCopy.getGoldenCudgel().setDisappear(true); System.out.println("淺複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear()); System.out.println("狀態重置..."); wuKong.getGoldenCudgel().setDisappear(false); WuKong wuKongDeepCopy = wuKong.deepClone(); System.out.println("深複製後悟空是否為同一個對象" + (wuKong == wuKongDeepCopy)); System.out.println("深複製後金箍棒是否為同一個對象" + (wuKong.getGoldenCudgel() == wuKongDeepCopy.getGoldenCudgel())); System.out.println("深複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear()); System.out.println("深複製對象金箍棒屬性發生改變..."); wuKongDeepCopy.getGoldenCudgel().setDisappear(true); System.out.println("深複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear()); } }
執行結果:
淺複製後悟空是否為同一個對象false
淺複製後金箍棒是否為同一個對象true
淺複製原對象金箍棒屬性為false
淺複製對象金箍棒屬性發生改變...
淺複製原對象金箍棒屬性為true
狀態重置...
深複製後悟空是否為同一個對象false
深複製後金箍棒是否為同一個對象false
深複製原對象金箍棒屬性為false
深複製對象金箍棒屬性發生改變...
深複製原對象金箍棒屬性為false
從執行結果中我們看到,淺複製後雖然悟空的複製對象與原對象不是同一個對象,但是兩個悟空持有的金箍棒是同一個對象,當複製對象的金箍棒消失時,原悟空對象的金箍棒也相應消失了,這顯然與我們的認知不符合,這時候,就需要深複製。
在深複製中,我們將需要深複製的屬性也實現了Cloneable介面,在這裡就是金箍棒類,在深複製deepClone方法中,我們不僅僅將悟空對象克隆了,同時也將需要深複製的對象克隆了一份,這樣,深複製後,兩個悟空持有的金箍棒就不是同一個了,複製對象的金箍棒消失,並不影響原悟空對象的金箍棒。
這裡也存在一個問題,就是當對象的屬性非常複雜的時候,我們的各個屬性都要去實現Cloneable介面,且deepClone()方法會相當複雜。
下麵我們看一下有沒有更加簡單的深複製方式
Java對象序列化可以將對象轉化為一個位元組序列,並能夠通過反序列化將位元組序列恢復為原來的對象,我們可以利用這一功能來實現輕量級的深複製,但前提是需要複製對象實現Serializable介面。
序列化深複製原型模式
悟空
package com.tirion.design.prototype; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class WuKong implements Serializable { private GoldenCudgel goldenCudgel; public GoldenCudgel getGoldenCudgel() { return goldenCudgel; } public void setGoldenCudgel(GoldenCudgel goldenCudgel) { this.goldenCudgel = goldenCudgel; } public WuKong() { goldenCudgel = new GoldenCudgel(false); } public WuKong deepClone() throws Exception { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); return (WuKong) ois.readObject(); } public static void main(String[] args) throws Exception { WuKong wuKong = new WuKong(); WuKong wuKongDeepCopy = wuKong.deepClone(); System.out.println("深複製後悟空是否為同一個對象" + (wuKong == wuKongDeepCopy)); System.out.println("深複製後金箍棒是否為同一個對象" + (wuKong.getGoldenCudgel() == wuKongDeepCopy.getGoldenCudgel())); System.out.println("深複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear()); System.out.println("深複製對象金箍棒屬性發生改變..."); wuKongDeepCopy.getGoldenCudgel().setDisappear(true); System.out.println("深複製原對象金箍棒屬性為" + wuKong.getGoldenCudgel().isDisappear()); } }
這時候運行main()方法會報java.io.NotSerializableException,因為序列化對象要求引用對象也必須實現Serializable介面,除非對應屬性不需要序列化,所以我們這裡需要將金箍棒類也實現序列化介面。
金箍棒
package com.tirion.design.prototype; import java.io.Serializable; public class GoldenCudgel implements Serializable { public GoldenCudgel() { } public GoldenCudgel(boolean disappear) { this.disappear = disappear; } private boolean disappear; public boolean isDisappear() { return disappear; } public void setDisappear(boolean disappear) { this.disappear = disappear; } public boolean equals(GoldenCudgel obj) { return obj.isDisappear() == disappear; } }
執行結果
深複製後悟空是否為同一個對象false
深複製後金箍棒是否為同一個對象false
深複製原對象金箍棒屬性為false
深複製對象金箍棒屬性發生改變...
深複製原對象金箍棒屬性為false
從結果來看,通過序列化實現深複製與通過clone()方法實現深複製的結果是一樣的,但是方法卻比較簡單,我們只需要將需要複製的對象實現序列化介面就可以了。同時java的對象序列化是提供了輕量級持久化的,我們可以通過網路或者磁碟來進行數據的傳播及持久化,並且就突破了clone()方法只能本地程式運行期間才能持久化的限制。
關於原型模式的介紹就到這裡,你可以將它記憶為身外身模式。
如果你認為文章中哪裡有錯誤或者不足的地方,歡迎在評論區指出,也希望這篇文章對你學習java設計模式能夠有所幫助。轉載請註明,謝謝。
更多設計模式的介紹請到悟空模式-java設計模式中查看。