相關文章
設計模式系列
前言
公眾號有同學留言設計模式,才發現好久沒有寫設計模式了。關於建立型設計模式只差原型模式沒寫了,這一篇就來填補這個空缺。
1.原型模式定義
原型模式定義
定義:用原型例項指定建立物件的種類,並通過拷貝這些原型建立新的物件。
原型模式UML圖
在原型模式中有如下角色:
- Client:客戶端角色。
- Prototype:抽象原型角色,抽象類或者介面,用來宣告clone方法。
- ConcretePrototype:具體的原型類,是客戶端角色使用的物件,即被複制的物件。
需要注意的是,Prototype通常是不用自己定義的,因為拷貝這個操作十分常用,Java中就提供了Cloneable介面來支援拷貝操作,它就是原型模式中的Prototype。當然,原型模式也未必非得去實現Cloneable介面,也有其他的實現方式。
2.原型模式簡單實現
原型模式的核心是clone方法,通過該方法進行拷貝,這裡舉一個名片拷貝的例子。
現在已經流行電子名片了,只要掃一下就可以將名片拷貝到自己的名片庫中, 我們先實現名片類。
具體的原型類
public class BusinessCard implements Cloneable {
private String name;
private String company;
public BusinessCard(){
System.out.println("執行建構函式BusinessCard");
}
public void setName(String name) {
this.name = name;
}
public void setCompany(String company) {
this.company = company;
}
@Override
public BusinessCard clone() {
BusinessCard businessCard = null;
try {
businessCard = (BusinessCard) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return businessCard;
}
public void show() {
System.out.println("name:" + name);
System.out.println("company:" + company);
}
}複製程式碼
BusinessCard類實現了Cloneable介面,它是一個標識介面,表示這個物件是可拷貝的,只要重寫clone方法就可以實現拷貝。如果實現了Cloneable介面卻沒有重寫clone方法就會報錯。需要注意的是,clone方法不是在Cloneable介面中定義的(Cloneable介面中沒有定義任何方法),而是在Object中定義的。
客戶端呼叫
public class Client {
public static void main(String[] args) {
BusinessCard businessCard = new BusinessCard();
businessCard.setName("錢三");
businessCard.setCompany("阿里");
//拷貝名片
BusinessCard cloneCard1 = businessCard.clone();
cloneCard1.setName("趙四");
cloneCard1.setCompany("百度");
BusinessCard cloneCard2 = businessCard.clone();
cloneCard2.setName("孫五");
cloneCard2.setCompany("騰訊");
businessCard.show();
cloneCard1.show();
cloneCard2.show();
}
}複製程式碼
除了第一個名片,其他兩個名片都是通過clone方法得到的,需要注意的是,clone方法並不會執行cloneCard1和cloneCard2的建構函式,執行結果為:
執行建構函式BusinessCard
name:錢三
company:阿里
name:趙四
company:百度
name:孫五
company:騰訊
3.淺拷貝和深拷貝
原型模式涉及到淺拷貝和深拷貝的知識點,為了更好的理解它們,還需要舉一些例子。
實現淺拷貝
上述的例子中,BusinessCard的欄位都是String型別的,如果欄位是引用的型別的,會出現什麼情況呢?如下所示。
public class DeepBusinessCard implements Cloneable {
private String name;
private Company company = new Company();
public void setName(String name) {
this.name = name;
}
public void setCompany(String name, String address) {
this.company.setName(name);
this.company.setAddress(address);
}
@Override
public DeepBusinessCard clone() {
DeepBusinessCard businessCard = null;
try {
businessCard = (DeepBusinessCard) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return businessCard;
}
public void show() {
System.out.println("name:" + name);
System.out.println("company:" + company.getName() + "-address-" + company.getAddress());
}
}複製程式碼
我們定義了DeepBusinessCard 類,它的欄位company是引用型別的,Company類如下所示。
public class Company {
private String name;
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}複製程式碼
在客戶端使用DeepBusinessCard:
public class Client {
public static void main(String[] args) {
DeepBusinessCard businessCard=new DeepBusinessCard();
businessCard.setName("錢三");
businessCard.setCompany("阿里","北京望京");
DeepBusinessCard cloneCard1=businessCard.clone();
cloneCard1.setName("趙四");
cloneCard1.setCompany("百度","北京西二旗");
DeepBusinessCard cloneCard2=businessCard.clone();
cloneCard2.setName("孫五");
cloneCard2.setCompany("騰訊","北京中關村");
businessCard.show();
cloneCard1.show();
cloneCard2.show();
}
}複製程式碼
執行結果為:
name:錢三
company:騰訊-address-北京中關村
name:趙四
company:騰訊-address-北京中關村
name:孫五
company:騰訊-address-北京中關村
從結果可以看出company欄位為最後設定的"騰訊"、"北京中關村"。這是因為Object類提供的clone方法,不會拷貝物件中的內部陣列和引用物件,導致它們仍舊指向原來物件的內部元素地址,這種拷貝叫做淺拷貝。
company欄位是引用型別,businessCard被拷貝後,company欄位仍舊指向原來的businessCard物件的company欄位的地址。這樣我們每次設定company欄位,都會覆蓋上一次設定的值,最終留下的就是最後一次設定的值:"騰訊"、"北京中關村"。
引用關係如下圖所示。
這樣的引用關係顯然不符合需求,有多個物件可以修改company,我們應該將引用關係改為如下形式:
拷貝businessCard物件的同時,也將它內部的引用物件company進行拷貝,使得每個拷貝的物件之間無任何關聯,都指向了自身對應的company,這種拷貝就是深拷貝。
實現深拷貝
首先需要修改Company類,如下所示。
public class Company implements Cloneable{
private String name;
private String address;
...
public Company clone(){
Company company=null;
try {
company= (Company) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return company;
}
}複製程式碼
為了實現Company類能被拷貝,Company類也需要實現Cloneable介面並且覆寫clone方法。接著修改DeepBusinessCard的clone方法:
public class DeepBusinessCard implements Cloneable {
private String name;
private Company company = new Company();
...
@Override
public DeepBusinessCard clone() {
DeepBusinessCard businessCard = null;
try {
businessCard = (DeepBusinessCard) super.clone();
businessCard.company = this.company.clone();//1
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return businessCard;
}
...
}複製程式碼
在註釋1處增加了對company欄位的拷貝處理。最後在客戶端呼叫,輸出的結果為:
name:錢三
company:阿里-address-北京望京
name:趙四
company:百度-address-北京西二旗
name:孫五
company:騰訊-address-北京中關村
4.原型模式的使用場景
- 如果類的初始化需要耗費較多的資源,那麼可以通過原型拷貝避免這些消耗。
- 通過new產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式。
- 一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以拷貝多個物件供呼叫者使用,即保護性拷貝。
5.原型模式的優缺點
優點
原型模式是在記憶體中二進位制流的拷貝,要比new一個物件的效能要好,特別是需要產生大量物件時。
缺點
直接在記憶體中拷貝,建構函式是不會執行的,這樣就減少了約束,這既是優點也是缺點,需要在實際應用中去考量。
參考資料
《大話設計模式》
《設計模式之禪》
《Android原始碼設計模式解析與實戰》
歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。