一 原型模式引入
原型模式作為建立型模式的最後一種,它並沒有涉及到很多的內容,我們來看一下
首先舉一個生活上的例子,例如我們要出版一本書,其中有一些資訊欄位,例如書名價格等等
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 中實現淺克隆,所以要注意深淺克隆運用得當
結尾
如果文章中有什麼不足,歡迎大家留言交流,感謝朋友們的支援!
如果能幫到你的話,那就來關注我吧!如果您更喜歡微信文章的閱讀方式,可以關注我的公眾號
在這裡的我們素不相識,卻都在為了自己的夢而努力 ❤
一個堅持推送原創開發技術文章的公眾號:理想二旬不止