【設計模式】第五篇:什麼是原型模式?淺提淺拷貝和深拷貝

BWH_Steven發表於2020-11-11

一 原型模式引入

原型模式作為建立型模式的最後一種,它並沒有涉及到很多的內容,我們來看一下

首先舉一個生活上的例子,例如我們要出版一本書,其中有一些資訊欄位,例如書名價格等等

public class Book {
    private String name; // 姓名
    private int price; // 價格
    private Partner partner; // 合作伙伴
    // 省略建構函式、get set、toString 等
}

引用型別 Partner 也很簡單

public class Partner{
    private String name;
    
// 省略建構函式、get set、toString 等
}

(一) 直接 new

書籍出版肯定不能只出一本,如何大批量生產呢?

有人或許想到,像下面這樣每一次都重新 new(甚至寫個 for 迴圈),我們先不說大量 new 的效率問題,首先每次一都需要重新給新物件複製,這不就像,我每刊印一本書,就得重新寫一次嗎???

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
		// 初始化一個合作伙伴型別
        Partner partner = new Partner("張三");
		// 帶參賦值
        Book bookA = new Book("理想二旬不止", 66, partner);
        Book bookB = new Book("理想二旬不止", 66, partner);

        System.out.println("A: " + bookA.toString());
        System.out.println("A: " + bookA.hashCode());
	    System.out.println("B: " + bookB.toString());
      	System.out.println("B: " + bookB.hashCode());

    }
}

有的同學還或許想到了,先把 A 建立出來,然後再賦值給 B、C ..... 等等,但是這種方式其實是傳遞引用而不是傳值,這就好比在 C 和 B 上寫著,內容詳情請看 A

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {

        // 初始化一個合作伙伴型別
        Partner partner = new Partner("張三");
        // 帶參賦值
        Book bookA = new Book("理想二旬不止", 66, partner);

        System.out.println("A: " + bookA.toString());
        System.out.println("A: " + bookA.hashCode());
        
        // 引用賦值
        Book bookB = bookA;
        
        System.out.println("B: " + bookB.toString());
        System.out.println("B: " + bookB.hashCode());
    }
}

這兩樣顯然是不行的,我們正常的思路是,作者只需要寫一次書籍內容,先刊印一本,如果能行,就照著這個樣本進行大批量同彩影印,而上面的傳引用方法也顯然不合適,這就需要用到Java克隆

(二) 淺克隆

用到克隆,首先就對 Book 類進行處理

  • 首先實現 Cloneable 介面
  • 接著重寫 clone 方法
public class Book implements Cloneable{
    private String name; // 姓名
    private int price; // 價格
    private Partner partner; // 合作伙伴

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    
    // 省略建構函式、get set、toString 等
}

再來測試一下

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 初始化一個合作伙伴型別
        Partner partner = new Partner("張三");
        // 帶參賦值
        Book bookA = new Book("理想二旬不止", 66, partner);
        // B 克隆 A
        Book bookB = (Book) bookA.clone();

        System.out.println("A: " + bookA.toString());
        System.out.println("A: " + bookA.hashCode());
        System.out.println("B: " + bookB.toString());
        System.out.println("B: " + bookB.hashCode());
    }
}

執行結果

A: Book{name='理想二旬不止', price=66, partner=Partner{name=張三}}
A: 460141958
B: Book{name='理想二旬不止', price=66, partner=Partner{name=張三}}
B: 1163157884

結果非常明顯,書籍資訊是一致的,但是記憶體地址是不一樣的,也就是說確實克隆成功了,列印其 hashCode 發現兩者並不相同,說明不止指向同一個,也是滿足我們要求的

到這裡並沒有結束,你會發現還是有問題,當你刊印的過程中修改一些值的內容的時候,你看看效果

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 初始化一個合作伙伴型別
        Partner partner = new Partner("張三");
        // 帶參賦值
        Book bookA = new Book("理想二旬不止", 66, partner);
        // B 克隆 A
        Book bookB = (Book) bookA.clone();
        // 修改資料
        partner.setName("李四");

        System.out.println("A: " + bookA.toString());
        System.out.println("A: " + bookA.hashCode());
        System.out.println("B: " + bookB.toString());
        System.out.println("B: " + bookB.hashCode());
    }
}

執行結果

A: Book{name='理想二旬不止', price=66, partner=Partner{name=李四}}
A: 460141958
B: Book{name='理想二旬不止', price=66, partner=Partner{name=李四}}
B: 1163157884

???這不對啊,B 明明是先克隆 A 的,為什麼我在克隆後,修改了 A 中一個的值,但是B也變化了啊

這就是典型的淺克隆,在 Book 類,當欄位是引用型別,例如 Partner 這個合作伙伴類,就是我們自定義的類,這種情況複製引用不賦值引用的物件,因此,原始物件和複製後的這個Partner物件是引用同一個物件的

(三) 深克隆

如何解決上面的問題呢,我們需要重新重寫 clone 的內容,同時在引用型別中也實現淺克隆

(1) 被引用型別實現淺克隆

全程式碼如下

public class Partner implements Cloneable {
    private String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    // 省略建構函式、get set、toString 等
}

(2) 修改引用類 cloen 方法

public class Book implements Cloneable{
    private String name; // 姓名
    private int price; // 價格
    private Partner partner; // 合作伙伴

    @Override
    protected Object clone() throws CloneNotSupportedException {
		Object clone = super.clone();
        Book book = (Book) clone;
        book.partner =(Partner) this.partner.clone();
        return clone;
    }
	// 省略建構函式、get set、toString 等
}

測試一下

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 初始化一個合作伙伴型別
        Partner partner = new Partner("張三");
        // 帶參賦值
        Book bookA = new Book("理想二旬不止", 66, partner);
        // B 克隆 A
        Book bookB = (Book) bookA.clone();
        // 修改資料
        partner.setName("李四");

        System.out.println("A: " + bookA.toString());
        System.out.println("A: " + bookA.hashCode());
        System.out.println("B: " + bookB.toString());
        System.out.println("B: " + bookB.hashCode());
    }
}

執行效果

A: Book{name='理想二旬不止', price=66, partner=Partner{name=李四}}
A: 460141958
B: Book{name='理想二旬不止', price=66, partner=Partner{name=張三}}
B: 1163157884

可以看到,B 克隆 A 後,修改 A 中 合作伙伴 的值,沒有受到影響,這也就是我們一開頭想要實現的效果了

【設計模式】第五篇:什麼是原型模式?淺提淺拷貝和深拷貝

二 原型模式理論

(一) 什麼是原型模式

在一些程式中,或許需要建立大量相同或者相似的物件,在建構函式的執行比較緩慢的時候,多次通過傳統的建構函式建立物件,就會複雜且耗資源,同時建立時的細節也一樣暴露了出來

原型模式就可以幫助我們解決這一問題

定義:原型模式,甩原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件

(二) 結構

根據結構圖簡單說一下其中的角色:

  • 抽象原型類(Prototype):規定了具體原型物件必須實現的介面
  • 具體原型類(ConcretePrototype):實現 clone 方法,即一個克隆自身的操作
  • 訪問類(Client):使用具體原型類中的 clone 方法克隆自身,從而建立一個新的物件

(三) 兩種模式

根據上面的例子也可以看出來了,原型模式分為淺克隆和深克隆

  • 淺克隆:建立一個新物件,新物件的屬性和原來物件完全相同,對於非基本型別屬性,仍指向原有屬性所指向的物件的記憶體地址
  • 深克隆:建立一個新物件,屬性中引用的其他物件也會被克隆,不再指向原有物件地址。

(四) 優缺點

優點:

  • Java 原型模式基於記憶體二進位制流複製,比直接 new 的效能會更好一些

  • 可以利用深克隆儲存物件狀態,存一份舊的(克隆出來),在對其修改,可以充當一個撤銷功能

缺點:

  • 需要配置 clone 方法,改造時需要對已有類進行修改,違背 “開閉原則”
  • 如果物件間存在多重巢狀引用時,每一層都需要實現克隆
    • 例如上例中,Book 中實現深克隆,Partner 中實現淺克隆,所以要注意深淺克隆運用得當

結尾

如果文章中有什麼不足,歡迎大家留言交流,感謝朋友們的支援!

如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公眾號

在這裡的我們素不相識,卻都在為了自己的夢而努力 ❤

一個堅持推送原創開發技術文章的公眾號:理想二旬不止

相關文章