原型模式介紹
原型模式(Prototype Pattern)是創(chuàng)建型模式的一種。這種模式是實(shí)現(xiàn)了一個(gè)原型接口,該接口用于創(chuàng)建當(dāng)前對(duì)象的克隆。當(dāng)直接創(chuàng)建對(duì)象的代價(jià)比較大時(shí),則采用這種模式。
原型模式定義
用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。
原型模式使用場(chǎng)景
- 如果類的初始化需要耗費(fèi)較多的資源,那么可以通過(guò)原型拷貝避免這些消耗。
- 通過(guò)new產(chǎn)生一個(gè)對(duì)象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問(wèn)權(quán)限,則可以使用原型模式。
- 一個(gè)對(duì)象需要提供給其他對(duì)象訪問(wèn),而且各個(gè)調(diào)用者可能都需要修改其值時(shí),可以拷貝多個(gè)對(duì)象供調(diào)用者使用,即保護(hù)性拷貝。
原型模式 UML 類圖

角色介紹:
- Client:客戶端角色。
- Prototype:抽象原型角色,抽象類或者接口,用來(lái)聲明clone方法。
- ConcretePrototype:具體的原型類,是客戶端角色使用的對(duì)象,即被復(fù)制的對(duì)象。
需要注意的是,Prototype 通常是不用自己定義的,因?yàn)榭截愡@個(gè)操作十分常用,Java 中就提供了Cloneable 接口來(lái)支持拷貝操作,它就是原型模式中的Prototype。當(dāng)然,原型模式也未必非得去實(shí)現(xiàn)Cloneable接口,也有其他的實(shí)現(xiàn)方式。
原型模式的實(shí)現(xiàn)
原型模式的核心是clone方法,通過(guò)該方法進(jìn)行拷貝,這里舉一個(gè)名片拷貝的例子。
現(xiàn)在已經(jīng)流行電子名片了,只要掃一下就可以將名片拷貝到自己的名片庫(kù)中, 我們先實(shí)現(xiàn)名片類。
Java 原型類就是 Cloneable 了
具體原型類
public class BusinessCard {
private String name;
private String company;
public BusinessCard() {
System.out.println("執(zhí)行構(gòu)造函數(shù)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 + ", company:" + company);
}
}
注意: Cloneable 是一個(gè)空接口,clone 方法是在 Object 類中定義,訪問(wèn)類型為 protected,當(dāng)執(zhí)行 clone 方法時(shí)會(huì)檢測(cè)是不是 Cloneable 類型,如果不是就會(huì)報(bào)java.lang.CloneNotSupportedException 錯(cuò)誤。
客戶端
public class Client {
public static void main(String[] args) {
BusinessCard businessCard = new BusinessCard();
businessCard.setName("小明");
businessCard.setCompany("百度");
BusinessCard cloneCard1 = businessCard.clone();
cloneCard1.setName("小強(qiáng)");
cloneCard1.setCompany("阿里");
BusinessCard cloneCard2 = businessCard.clone();
cloneCard2.setName("小紅");
cloneCard2.setCompany("騰訊");
businessCard.show();
cloneCard1.show();
cloneCard2.show();
}
}
除了第一個(gè)名片,其他兩個(gè)名片都是通過(guò)clone方法得到的,需要注意的是,clone方法返回的對(duì)象是在內(nèi)存中二進(jìn)制拷貝的原對(duì)象,并不會(huì)執(zhí)行其構(gòu)造方法。運(yùn)行結(jié)果為:
執(zhí)行構(gòu)造函數(shù)BusinessCard
name:小明, company:百度
name:小強(qiáng), company:阿里
name:小紅, company:騰訊
淺拷貝和深拷貝
原型模式涉及到淺拷貝和深拷貝的知識(shí)點(diǎn),為了更好的理解它們,還需要舉一些例子。
實(shí)現(xiàn)淺拷貝
上述的例子中,BusinessCard 的字段都是String類型的,如果字段是引用的類型的,會(huì)出現(xiàn)什么情況呢?如下所示:
public class BusinessCard implements Cloneable {
private String name;
private Company company = new Company();
public BusinessCard() {
System.out.println("執(zhí)行構(gòu)造函數(shù)BusinessCard");
}
public void setName(String name) {
this.name = name;
}
public void setCompany(String name, String address) {
company.name = name;
company.address = address;
}
@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 + ", company:" + company);
}
}
public class Company {
String name;
String address;
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
客戶端代碼
public class Client {
public static void main(String[] args) {
BusinessCard businessCard = new BusinessCard();
businessCard.setName("小明");
businessCard.setCompany("百度", "北京");
BusinessCard cloneCard1 = businessCard.clone();
cloneCard1.setName("小強(qiáng)");
cloneCard1.setCompany("阿里", "杭州");
BusinessCard cloneCard2 = businessCard.clone();
cloneCard2.setName("小紅");
cloneCard2.setCompany("騰訊", "深圳");
businessCard.show();
cloneCard1.show();
cloneCard2.show();
}
}
執(zhí)行結(jié)果:
執(zhí)行構(gòu)造函數(shù)BusinessCard
name:小明, company:Company{name='騰訊', address='深圳'}
name:小強(qiáng), company:Company{name='騰訊', address='深圳'}
name:小紅, company:Company{name='騰訊', address='深圳'}
從結(jié)果可以看出 company 字段為最后設(shè)置的”騰訊”、”深圳”。這是因?yàn)?Object 類提供的 clone 方法,不會(huì)執(zhí)行對(duì)象中的內(nèi)部數(shù)組和引用對(duì)象的 clone 方法,導(dǎo)致 clone 出的對(duì)象的 company 指向的對(duì)象仍舊是原型中的 company 指向的對(duì)象。這種拷貝叫做淺拷貝。
淺拷貝顯然不符號(hào)要求,實(shí)際開(kāi)發(fā)時(shí)我們希望每個(gè)對(duì)象可以獨(dú)立變化,此時(shí)我們就需要通過(guò)深拷貝來(lái)解決,也就是執(zhí)行 clone 方法時(shí),也需要執(zhí)行內(nèi)部對(duì)象的 clone 方法。
實(shí)現(xiàn)深拷貝
首先需要讓 Company 支持 clone,修改代碼如下:
public class Company implements Cloneable {
String name;
String address;
@Override
public Company clone() {
Company company = null;
try {
company = (Company) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return company;
}
@Override
public String toString() {
return "Company{" +
"name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
修改 BusinessCard 的 clone 方法
public class BusinessCard implements Cloneable {
...
@Override
public BusinessCard clone() {
BusinessCard businessCard = null;
try {
businessCard = (BusinessCard) super.clone();
businessCard.company = company.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return businessCard;
}
}
Client 不變,運(yùn)行結(jié)果如下:
執(zhí)行構(gòu)造函數(shù)BusinessCard
name:小明, company:Company{name='百度', address='北京'}
name:小強(qiáng), company:Company{name='阿里', address='杭州'}
name:小紅, company:Company{name='騰訊', address='深圳'}
總結(jié)
原型模式比較簡(jiǎn)單,核心問(wèn)題就是對(duì)原始對(duì)象進(jìn)行拷貝,需要注意的就是深、淺拷貝的問(wèn)題。開(kāi)發(fā)中,為了避免操作副本時(shí)影響原始對(duì)象,應(yīng)盡量采用深拷貝。
優(yōu)點(diǎn)
原型模式是在內(nèi)存中二進(jìn)制流的拷貝,要比 new 一個(gè)對(duì)象的性能要好,特別是需要產(chǎn)生大量對(duì)象時(shí)。
缺點(diǎn)
直接在內(nèi)存中拷貝,構(gòu)造函數(shù)是不會(huì)執(zhí)行的,這樣就減少了約束,這既是優(yōu)點(diǎn)也是缺點(diǎn),需要在實(shí)際應(yīng)用中去考量。
Android 源碼中的原型模式
Android 源碼中的常用的 Intent 就實(shí)現(xiàn)了 clone 方法。
public class Intent implements Parcelable, Cloneable {
public Intent(Intent o) {
this(o, COPY_MODE_ALL);
}
private Intent(Intent o, @CopyMode int copyMode) {
this.mAction = o.mAction;
this.mData = o.mData;
this.mType = o.mType;
this.mPackage = o.mPackage;
this.mComponent = o.mComponent;
if (o.mCategories != null) {
this.mCategories = new ArraySet<>(o.mCategories);
}
if (copyMode != COPY_MODE_FILTER) {
this.mFlags = o.mFlags;
this.mContentUserHint = o.mContentUserHint;
this.mLaunchToken = o.mLaunchToken;
if (o.mSourceBounds != null) {
this.mSourceBounds = new Rect(o.mSourceBounds);
}
if (o.mSelector != null) {
this.mSelector = new Intent(o.mSelector);
}
if (copyMode != COPY_MODE_HISTORY) {
if (o.mExtras != null) {
this.mExtras = new Bundle(o.mExtras);
}
if (o.mClipData != null) {
this.mClipData = new ClipData(o.mClipData);
}
} else {
if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
this.mExtras = Bundle.STRIPPED;
}
}
}
}
@Override
public Object clone() {
return new Intent(this);
}
...
}
可以看到 clone 方法實(shí)際上內(nèi)部沒(méi)有調(diào)用 super.clone() 方法來(lái)實(shí)現(xiàn)對(duì)象拷貝,而是調(diào)用了 new Intent(this)??梢钥吹?Intent 中有很多參數(shù),使用 clone 即可增加構(gòu)建對(duì)象的便捷性,在 clone 中使用 new,可以實(shí)現(xiàn)定制化拷貝對(duì)象,如果使用 super.clone() 則會(huì)將原對(duì)象進(jìn)行拷貝。
使用 new 和 clone 需要根據(jù)構(gòu)造對(duì)象的成本來(lái)決定,如果對(duì)象的構(gòu)造成本比較高或者比較麻煩,那么使用 clone 方法效率更高,否則就使用 new。