原型模式
- Javascript的繼承就是通過原型鏈實現的
- 原型模式中有個概念,叫克隆 ,例如我們在拷貝文件的時候,將文件A複製一份成文件B,那麼文件A 就是原型,文件B就是複製拷貝出來的一份,克隆也類似於這樣
- 那麼原型模式什麼時候會使用呢?
- 原型模式屬於創建型模式,假設我們要創建一個很複雜的對象A,需要經過一系列複雜的步驟,如果還要在創建一份,來一個對象B,那麼再經過一系列複雜的步驟去創建就會顯得很愚蠢,這個時候,就會很機智的想到我可以直接把A複製一份啊,這樣不就有B了嗎。
- 又例如你要用別人的網站,那你只需要把代碼拷貝一份,改一改裏面的一些細節,就可以直接使用了,大大提高了效率。
- 思路:Java中
Object類
是所有類的根類,Object類提供了一個clone()
方法,該方法可以將一個Java對象複製一份,但是需要實現clone
的Java類必須要實現一個接口Cloneable
,該接口表示該類能夠複製且具有複製的能力 ==> 原型模式
Java中的克隆技術
- 以某個對象爲原型,克隆出來一份新的對象,兩個對象是一樣的,例如Object對象的
clone()
方法以及Cloneable接口
- 要對文件進行復制拷貝,也許我們會想到Java中的IO流,的確我們可以通過IO流對文件進行復制拷貝,但是用Java實現的IO流對文件進行操作,在效率上會比較低
- 而真正的複製拷貝應該是在內存中進行操作的,如果我們用Java去操作內存,就會很麻煩
Object
對象的clone
方法是一個native
的方法,也就是說會通過Java去調用本地的接口,也就是調用本地用C/C++寫的方法,通過這個方法實現克隆,效率就會更高。
- 那麼克隆出來的對象和原來的對象有沒有區別呢?那肯定是有區別的。
- new 是創建一個對象,裏面可能有一些默認值之類的;但是克隆出來的對象就是原封不動的拷貝一份,就相當於多了一個副本,你可以針對副本進行修改,但是原來的對象不會有變化
舉個例子 - 盜版書和正版書之間的故事
import java.util.Date;
public class Book implements Cloneable{
private String name;
private String author;
private Date publishDate;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
--------------------------------------------------------------------------
public class Market {
public static void main(String[] args) throws CloneNotSupportedException {
Book proto = new Book("Java從入門到放棄","Oracle",new Date());
Book clone = (Book) proto.clone();
System.out.println("proto ==>> "+ proto + " HashCode:"+proto.hashCode());
System.out.println("clone ==>> "+ clone + " HashCode:"+clone.hashCode());
System.out.println("==================================");
clone.setName("Java成長之路");
System.out.println("修改後的clone ==>> "+ clone);
System.out.println("proto ==>> "+ proto);
System.out.println("==================================");
clone.getPublishDate().setTime(123123123);
System.out.println("改變了Date的clone ==>> "+ clone + " HashCode:"+clone.hashCode());
System.out.println("proto ==>> "+ proto + " HashCode:"+proto.hashCode());
System.out.println("==================================");
}
}
- 在這段代碼裏面,我們會發現:
(1)克隆出來的對象和原型對象的確是兩個不同的對象
(2)修改克隆對象的值不會對原型對象產生影響
(3)如果修改了克隆對象引用的對象,會影響到原型對象
- 那麼我們在使用原型模式在克隆的時候,就要注意對象的引用關係
淺克隆改造成深克隆
- 淺拷貝的介紹
- 對於數據類型是基本數據類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新的對象。
- 對於數據類型是引用數據類型的成員變量,比如說成員變量是某個數組、某個類的對象等,那麼淺拷貝會進行引用傳遞,也就是只是將該成員變量的引用值(內存地址)複製一份給新的對象。因爲實際上兩個對象的該成員變量都指向同一個實例。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的該成員變量值
- 深拷貝基本介紹
- 複製對象的所有基本數據類型的成員變量值
- 爲所有引用數據類型的成員變量申請存儲空間,並複製每個引用數據類型成員變量所引用的對象,直到該對象可達的所有對象。也就是說,對象進行深拷貝要對整個對象進行拷貝
- 深拷貝實現方式
- 1:重寫clone方法來實現深拷貝
- 2:通過對象序列化實現深拷貝
- 深克隆的實現方式有很多,其中有序列化和反序列化,但是一旦和IO進行交互,就會影響程序的效率,不建議使用,有興趣也可以參考一下下面這段代碼實現一下
- 而重寫clone方法對於屬性較多的時候,雖然這麼操作會很繁瑣,但是這樣做確實是比較好的辦法
@Override
protected Object clone() throws CloneNotSupportedException {
Object newClone = super.clone();
Book book = (Book) newClone;
book.publishDate = (Date) this.publishDate.clone();
return newClone;
}
- 再進行相同的操作,將clone的date對象的值進行修改
輸出結果
改變了Date的clone ==>> Book{name='Java從入門到放棄', author='Oracle', publishDate=Fri Jan 02 18:12:03 CST 1970}
proto ==>> Book{name='Java從入門到放棄', author='Oracle', publishDate=Mon Mar 23 17:05:39 CST 2020}
小結
- 創建新的對象比較複雜時,可以利用原型模式簡化對象的創建過程,同時也能夠提高效率
- 不用重新初始化對象,而是動態地獲得對象運行時的狀態
- 如果原始對象發生變化(增加或者減少屬性),其它克隆對象的也會發生相應的變化,無需修改代碼
- 在實現深克隆的時候可能需要比較複雜的代碼
- 缺點:需要爲每一個類配備一個克隆方法,這對全新的類來說不是很難,但對已有的類進行改造時,需要修改其源代碼,違背了ocp原則
應用場景
- Spring中的Bean的創建,就是使用了單例模式和原型模式的設計思想,從而節省了空間,提高了效率
- 通常原型模式和工廠模式也會搭配使用,將工廠模式中創建對象的new替換成原型模式