1 原型模式介紹
原型模式(Prototype
)是一個創(chuàng)建型的模式,原型模式是有一個共有信息的樣板實例跨释,然后拷貝這個樣板實例胸私,而復(fù)制后的實例就是所謂的“原型”,這個原型是可以修改的鳖谈。原型模式多用于創(chuàng)建復(fù)雜的或者構(gòu)造耗時的實例岁疼,因為這種情況下, 復(fù)制一個已經(jīng)存在的實例可以使程序運行更高效缆娃。
2 原型模式定義
用原型實例指定創(chuàng)建對象的種類捷绒,并通過拷貝這些原型創(chuàng)建新的對象
3 原型模式UML類圖
在原型模式中有如下角色:
-
Client
:客戶端角色。 -
Prototype
:抽象原型角色贯要,抽象類或者接口暖侨,用來聲明clone
方法。 -
ConcretePrototype
:具體的原型類崇渗,是客戶端角色使用的對象字逗,即被復(fù)制的對象。
4 原型模式的使用場景
- 類初始化需要消耗非常多的資源宅广,這個資源包括數(shù)據(jù)葫掉、硬件資源等。通過原型拷貝避免這些消耗跟狱。
- 通過
new
產(chǎn)生一個獨享需要非常頻繁的數(shù)據(jù)準(zhǔn)備或訪問權(quán)限俭厚。 - 一個對象需要提供給其他對象訪問,而且各個調(diào)用者可能都需要修改其值時驶臊,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用挪挤,即保護(hù)性拷貝叼丑。
5 原型模式使用示例
下面我們模擬一個發(fā)送短信的例子,來看下原型模式的簡單使用:
其中Message
類扮演的是ConcretePrototype
扛门,Cloneable
就是Prototype
具體的原型類:
public class Message implements Cloneable{
private String name;
private double money;
public Message() {
System.out.println("執(zhí)行構(gòu)造函數(shù)Message");
}
public void setMessage(String name, double money) {
this.name = name;
this.money = money;
}
@NonNull
@Override
public Message clone() {
Message message = null;
try {
message = (Message) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return message;
}
public void sendMessage(){
System.out.println(name +"您好:您今天消費了"+money+"元");
}
}
Message
類實現(xiàn)了Cloneable
接口鸠信,它是一個標(biāo)識接口,表示這個對象是可拷貝的尖飞,只要重寫clone
方法就可以實現(xiàn)拷貝症副。
這里需要注意的是clone
方法并不是Cloneable
接口中的店雅,而是Object
中的方法政基。
下面我們看下客戶端實現(xiàn):
public class MbClient {
public static void main(String[] args){
Message message = new Message();
message.setMessage("張三",100);
Message message1 = message.clone();
message1.setMessage("李四",200);
Message message2 = message.clone();
message2.setMessage("王五",300);
message.sendMessage();
message1.sendMessage();
message2.sendMessage();
}
}
我們可以看到李四和王五的消息是通過clone
方法克隆的,而clone
方法是不會執(zhí)行構(gòu)造函數(shù)的闹啦。輸出的結(jié)果如下:
//執(zhí)行構(gòu)造函數(shù)Message
//張三您好:您今天消費了100.0元
//李四您好:您今天消費了200.0元
//王五您好:您今天消費了300.0元
如果Message
類沒有實現(xiàn)了Cloneable
接口沮明,就去直接調(diào)用clone
方法,就會拋出異常窍奋。輸出結(jié)果如下:
執(zhí)行構(gòu)造函數(shù)Message
java.lang.CloneNotSupportedException: com.monkey.myapplication.mbdemo.Message
at java.lang.Object.clone(Native Method)
at com.monkey.myapplication.mbdemo.Message.clone(Message.java:27)
at com.monkey.myapplication.mbdemo.MbClient.main(MbClient.java:13)
Exception in thread "main" java.lang.NullPointerException
at com.monkey.myapplication.mbdemo.MbClient.main(MbClient.java:14)
6 淺拷貝和深拷貝
由于Object
類提供的clone
方法荐健,不會拷貝對象中的內(nèi)部數(shù)組和引用對象,所以就有了淺拷貝和深拷貝琳袄。
6.1 淺拷貝
我們繼續(xù)使用發(fā)送短信的例子來看下淺拷貝江场,在Message
類中有一個消費明細(xì)對象。
public class Message implements Cloneable{
private String name; //姓名
private ExpenseDetail detail;//消費明細(xì)
public Message() {
System.out.println("執(zhí)行構(gòu)造函數(shù)Message");
detail = new ExpenseDetail();
}
public void setMessage(String name, String type,double money) {
this.name = name;
this.detail.setType(type);
this.detail.setMoney(money);
}
@NonNull
@Override
public Message clone() {
Message message = null;
try {
message = (Message) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return message;
}
public void sendMessage(){
System.out.println(name +"您好:您今天"+detail.getType()+"消費了"+detail.getMoney()+"元");
}
}
消費明細(xì)類
public class ExpenseDetail{
private String type;
private double money;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
客戶端類
public class MbClient {
public static void main(String[] args){
Message message = new Message();
message.setMessage("張三","吃飯",10);
Message message1 = message.clone();
message1.setMessage("李四","看電影",50);
Message message2 = message.clone();
message2.setMessage("王五","買書",100);
message.sendMessage();
message1.sendMessage();
message2.sendMessage();
}
}
輸出的結(jié)果如下:
執(zhí)行構(gòu)造函數(shù)Message
張三您好:您今天買書消費了100.0元
李四您好:您今天買書消費了100.0元
王五您好:您今天買書消費了100.0元
我們可以看到所有人的消費明細(xì)居然都一樣窖逗,這是因為Object
類提供的clone
方法址否,不會拷貝對象中的內(nèi)部數(shù)組和引用對象,導(dǎo)致它們?nèi)耘f指向原來對象的內(nèi)部元素地址碎紊,這種拷貝叫做淺拷貝佑附。
由此而導(dǎo)致最后一次的值會覆蓋前一次的值。
6.2 深拷貝
public class Message implements Cloneable{
...
@NonNull
@Override
public Message clone() {
Message message = null;
try {
message = (Message) super.clone();
message.detail = this.detail.clone();//拷貝消費明細(xì)
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return message;
}
...
}
public class ExpenseDetail implements Cloneable{
private String type;
private double money;
...
@NonNull
@Override
protected ExpenseDetail clone(){
ExpenseDetail detail = null;
try {
detail = (ExpenseDetail) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return detail;
}
}
使用淺拷貝的客戶端代碼再次執(zhí)行后仗考,輸出的結(jié)果如下:
執(zhí)行構(gòu)造函數(shù)Message
張三您好:您今天吃飯消費了10.0元
李四您好:您今天看電影消費了50.0元
王五您好:您今天買書消費了100.0元
拷貝Message
對象的同時音同,也將它內(nèi)部的引用對象ExpenseDetail
進(jìn)行拷貝,使得每個拷貝的對象之間無任何關(guān)聯(lián)秃嗜,都指向了自身對應(yīng)的ExpenseDetail
权均,這種拷貝就是深拷貝。
7 總結(jié)
原型模式本質(zhì)上就是對象拷貝锅锨。使用原型模式可以解決構(gòu)建復(fù)雜對象的資源消耗問題螺句,能夠在某些場景下提升創(chuàng)建對象的效率,還有一個特點就是保護(hù)性拷貝橡类,如果我們操作時蛇尚,不會對原有的對象造成影響。
優(yōu)點:
原型模式是在內(nèi)存中二進(jìn)制流的拷貝顾画,要比new一個對象的性能要好取劫,特別是需要生產(chǎn)大量對象時匆笤。
缺點:
直接在內(nèi)存中拷貝,構(gòu)造函數(shù)是不會執(zhí)行的谱邪,這樣就減少了約束炮捧,既是優(yōu)點也是缺點,在實際開發(fā)當(dāng)中應(yīng)注意這個問題惦银。