原型模式(Prototype Pattern)
什么是原型模式呢?其實(shí)就是使用原型實(shí)例指定創(chuàng)建對象的種類,并且通過克隆這些原型創(chuàng)建新的對象。
1.淺克隆
大家可能對 PPT中的模版都不陌生,是的,有的時(shí)候套用別人已經(jīng)做好的模版可以很顯著的提高工作效率。當(dāng)提供者做好一個(gè)模版后,發(fā)布到網(wǎng)上,我們只需要輕輕的點(diǎn)擊一下使用此模版,就可以在這個(gè)模版的基礎(chǔ)上開始自己的工作,是不是感覺很像 Ctrl + C 的功能?其實(shí)原型模式提供的正是克隆功能。下面就讓我們來模仿PPT中模版的工作模式,詳細(xì)介紹原型模式
Java 也為我們提供了一個(gè)clone() 方法,那么現(xiàn)在使用Java 提供的方法,來實(shí)現(xiàn)這個(gè)效果吧
public class AModal implements Cloneable{
private String mHeader;
private String mContent;
public String getHeader() {
return mHeader;
}
public void setHeader(String header) {
mHeader = header;
}
public String getContent() {
return mContent;
}
public void setContent(String content) {
mContent = content;
}
public AModal clone(){
Object object = null;
try {
object = super.clone();
return (AModal)object;
} catch (CloneNotSupportedException e) {
System.out.println("Not support clone");
return null;
}
}
}
這個(gè)時(shí)候在主函數(shù)這樣調(diào)用
public class Main {
public static void main(String[] args){
AModal modal = new AModal();
modal.setHeader("歡迎你");
modal.setContent("你好");
System.out.println(modal.getHeader());
System.out.println(modal.getContent());
AModal my = modal.clone();
System.out.println(my.getHeader());
System.out.println(my.getContent());
}
}
這個(gè)時(shí)候我們只需要設(shè)置好模版,就可以盡情的使用克隆方法了,避免了我們做大量的重復(fù)勞動,非常的方便。
既然方法奏效了,那么可能出現(xiàn)一個(gè)疑問,當(dāng)我們改變克隆對象的時(shí)候,原型也會隨之改變嗎?既然問題出現(xiàn)了,那么我們可以到主函數(shù)中去驗(yàn)證一番。
首先,看看我們的克隆版本和原型是不是同一個(gè)對象呢?調(diào)用以下語句
System.out.println(modal == my);
輸出的結(jié)果為false,這說明,原型和克隆對象不是同一個(gè)對象,既然不是同一個(gè)對象,那么改變克隆對象自然不會影響到原型的內(nèi)容。
這種克隆方式固然簡單方便,但是它有一個(gè)很大的缺陷,就是它只能克隆值類型的數(shù)據(jù),而對于引用類型的數(shù)據(jù),只能復(fù)制一份引用對象的地址。現(xiàn)在我們就為AModal 添加一個(gè)背景音樂類,看看在克隆對象和原型中的背景音樂是否指向同一個(gè)對象。
public class BackgroundSong {
private String music;
public String getMusic() {
return music;
}
public void setMusic(String music) {
this.music = music;
}
}
在AModal 中添加私有變量BackgroundSong,然后添加setter 和 getter 方法,然后在主函數(shù)中調(diào)用以下語句
System.out.println(my.getSong() == modal.getSong());
結(jié)果發(fā)現(xiàn)輸出結(jié)果為true, 也就是說克隆對象中的BackgroundSong 和原型中是同一個(gè)對象,這個(gè)時(shí)候就比較棘手了,因?yàn)槲覀円坏└淖兛寺ο笾械倪@一變量,原型也會隨之改變,這不是我們想要的結(jié)果。
怎么做才能讓引用類型也原樣拷貝一份給克隆對象呢?這就是深克隆模式~
2.深克隆
在Java 中,如果需要實(shí)現(xiàn)深克隆,可以通過序列化方式來實(shí)現(xiàn)。序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個(gè)復(fù)制,而原對象仍然存在于內(nèi)存中,不發(fā)生任何改變。通過序列化實(shí)現(xiàn)的復(fù)制不僅可以復(fù)制對象本身,而且還可以復(fù)制其引用的成員對象。能夠?qū)崿F(xiàn)序列化的對象其類必須實(shí)現(xiàn)Serializable接口。
其實(shí)Serializable 接口和Cloneable 接口一樣,它們都是空接口,這種接口也叫做標(biāo)識接口,這種接口中沒有任何方法的定義,其存在的意義就是告訴JRE該接口的實(shí)現(xiàn)類是否具有某項(xiàng)功能。
下面來改寫B(tài)ackgoundSong,讓其繼承自Serializable接口
public class BackgroundSong implements Serializable{
private String music;
public String getMusic() {
return music;
}
public void setMusic(String music) {
this.music = music;
}
}
這個(gè)時(shí)候我們將摒棄Java 提供給我們的clone() 方法,我們將在AModal 中 增加一個(gè)自定義的deepClone()方法,如下所示
public AModal deepClone() throws IOException, ClassNotFoundException{
ByteArrayOutputStream mOutputStream = new ByteArrayOutputStream();
ObjectOutputStream mOutput = new ObjectOutputStream(mOutputStream);
mOutput.writeObject(this);
ByteArrayInputStream mInputStream = new ByteArrayInputStream(mOutputStream.toByteArray());
ObjectInputStream mInput = new ObjectInputStream(mInputStream);
return (AModal) mInput.readObject();
}
注意這個(gè)時(shí)候AModal 不再繼承自Cloneable,而是繼承自Serializable
public class AModal implements Serializable
這個(gè)時(shí)候在看看原型中的BackgroundSong 是否與克隆版仍為同一對象,調(diào)用一下語句
System.out.println(my.getSong() == modal.getSong());
結(jié)果這個(gè)時(shí)候返回的結(jié)果為false,這也就意味著此時(shí)的克隆,不僅僅只復(fù)制值類型的數(shù)據(jù),而是連同引用對象一起復(fù)制。這個(gè)時(shí)候原型對象就與克隆對象完全獨(dú)立了,可以放心使用克隆對象了。
原型模式的優(yōu)點(diǎn):
1.當(dāng)要創(chuàng)建的類比較復(fù)雜時(shí),使用此方法可以簡化創(chuàng)建對象的過程。
2.可以用于保存對象的狀態(tài),使用原型模式可以將對象的某一狀態(tài)保存下來,可以實(shí)現(xiàn)撤銷功能。
缺點(diǎn):
1.想要使用原型模式就需要為類配備clone() 方法,實(shí)現(xiàn)起來非常繁瑣,尤其是深克隆方法。