【23種設計模式】讓代碼來告訴你什麼叫原型模式

原型模式

  • 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;

// 實現克隆: 1.實現Cloneable接口  2.重寫clone()方法
public class Book implements Cloneable{
    // 很多的盜版書本 克隆別人寫出來的書本
    private String name;  //書名
    private String author;  //作者
    private Date publishDate;  //出版日期

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * 此處省略一些代碼
     * 無參構造
     * 有參構造
     * get set方法
     * toString方法
     */
}
--------------------------------------------------------------------------
// 克隆
// 在市場這個平臺裏面複製正版書籍,獲得盜版書本
// Market就是一個平臺,商家在裏面克隆正版書籍進行售賣 賺錢
public class Market {
    public static void main(String[] args) throws CloneNotSupportedException {
    
        // 原型對象 - 正版
        Book proto = new Book("Java從入門到放棄","Oracle",new Date());
        // 如果不盜版書本,那麼就要自己生產,就需要自己 new Book()
        // 克隆對象 - 盜版
        Book clone = (Book) proto.clone();  //可能會產生克隆不支持的異常
        System.out.println("proto ==>> "+ proto + " HashCode:"+proto.hashCode());
        System.out.println("clone ==>> "+ clone + " HashCode:"+clone.hashCode());
        System.out.println("==================================");
        /**
         * 執行結果
         * proto ==>> Book{name='Java從入門到放棄', author='Oracle', publishDate=Mon Mar 23 16:50:28 CST 2020} HashCode:317574433
         * clone ==>> Book{name='Java從入門到放棄', author='Oracle', publishDate=Mon Mar 23 16:50:28 CST 2020} HashCode:1494279232
         * 可以從hashcod中看出克隆的對象和原型是一摸一樣,但是兩個hashcode不一樣,的確是兩個不同的對象
         */


        // 這個時候,盜版的人準備換個書名再拿去賣  --
        clone.setName("Java成長之路");
        System.out.println("修改後的clone ==>> "+ clone);
        System.out.println("proto ==>> "+ proto);
        System.out.println("==================================");
        /**
         * 執行結果
         * 修改後的clone ==>> Book{name='Java成長之路', author='Oracle', publishDate=Mon Mar 23 16:50:28 CST 2020}
         * proto ==>> Book{name='Java從入門到放棄', author='Oracle', publishDate=Mon Mar 23 16:50:28 CST 2020}
         */

        // 但是這樣的克隆是有問題的,克隆分爲淺克隆和深克隆,如果是淺克隆,那麼克隆對象和原型對象裏面會引用同一個對象
        // 如果這裏我們修改clone對象引用的Date對象,按照預想的方向,應該是clone對象和proto對象的應該是不一樣的
        // 而name因爲是String,是基本數據類型,所以可以不用重寫clone方法. String內容修改直接返回新的對象.
        clone.getPublishDate().setTime(123123123);
        System.out.println("改變了Date的clone ==>> "+ clone + " HashCode:"+clone.hashCode());
        System.out.println("proto ==>> "+ proto + " HashCode:"+proto.hashCode());
        System.out.println("==================================");
        /**
         * 輸出結果
         * 改變了Date的clone ==>> Book{name='Java成長之路', author='Oracle', publishDate=Fri Jan 02 18:12:03 CST 1970} HashCode:1494279232
         * proto ==>> Book{name='Java從入門到放棄', author='Oracle', publishDate=Fri Jan 02 18:12:03 CST 1970} HashCode:317574433
         */

        // 那麼我們想要的應該是互不關聯的,clone對象與proto對象的date屬性引用的不是同一個,他們各自都有一個自己的date對象,這就叫深克隆,兩個對象的值互不影響
    }
}
  • 在這段代碼裏面,我們會發現:
    (1)克隆出來的對象和原型對象的確是兩個不同的對象
    (2)修改克隆對象的值不會對原型對象產生影響
    (3)如果修改了克隆對象引用的對象,會影響到原型對象
  • 那麼我們在使用原型模式在克隆的時候,就要注意對象的引用關係
    在這裏插入圖片描述

淺克隆改造成深克隆

  • 淺拷貝的介紹
    • 對於數據類型是基本數據類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新的對象。
    • 對於數據類型是引用數據類型的成員變量,比如說成員變量是某個數組、某個類的對象等,那麼淺拷貝會進行引用傳遞,也就是只是將該成員變量的引用值(內存地址)複製一份給新的對象。因爲實際上兩個對象的該成員變量都指向同一個實例。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的該成員變量值
  • 深拷貝基本介紹
    • 複製對象的所有基本數據類型的成員變量值
    • 爲所有引用數據類型的成員變量申請存儲空間,並複製每個引用數據類型成員變量所引用的對象,直到該對象可達的所有對象。也就是說,對象進行深拷貝要對整個對象進行拷貝
    • 深拷貝實現方式
      • 1:重寫clone方法來實現深拷貝
      • 2:通過對象序列化實現深拷貝
  • 深克隆的實現方式有很多,其中有序列化和反序列化,但是一旦和IO進行交互,就會影響程序的效率不建議使用,有興趣也可以參考一下下面這段代碼實現一下
    在這裏插入圖片描述
  • 而重寫clone方法對於屬性較多的時候,雖然這麼操作會很繁瑣,但是這樣做確實是比較好的辦法
// 在Book對象裏面的clone方法中進行改造,而不是直接引用符類的clone方法
@Override
    protected Object clone() throws CloneNotSupportedException {
        //改造clone方法,實現深克隆
        // 1 獲得已經clone的對象
        Object newClone = super.clone();
        // 2 強轉成Book對象
        Book book = (Book) newClone;
        // 3 將當前的Book對象的date屬性也進行克隆再返還給剛剛獲得到的clone的book
        book.publishDate = (Date) this.publishDate.clone();
        // 4 返回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}

小結

  1. 創建新的對象比較複雜時,可以利用原型模式簡化對象的創建過程,同時也能夠提高效率
  2. 不用重新初始化對象,而是動態地獲得對象運行時的狀態
  3. 如果原始對象發生變化(增加或者減少屬性),其它克隆對象的也會發生相應的變化,無需修改代碼
  4. 在實現深克隆的時候可能需要比較複雜的代碼
  5. 缺點:需要爲每一個類配備一個克隆方法,這對全新的類來說不是很難,但對已有的類進行改造時,需要修改其源代碼,違背了ocp原則

應用場景

  • Spring中的Bean的創建,就是使用了單例模式和原型模式的設計思想,從而節省了空間,提高了效率
  • 通常原型模式和工廠模式也會搭配使用,將工廠模式中創建對象的new替換成原型模式
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章