3分鐘學(xué)設(shè)計(jì)模式(創(chuàng)建型):5、原型模式
前言
設(shè)計(jì)模式,是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。它描述了在軟件設(shè)計(jì)過程中的一些不斷重復(fù)發(fā)生的問題,以及該問題的解決方案。也就是說,它是解決特定問題的一系列套路,是前輩們的代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié),具有一定的普遍性,可以反復(fù)使用。其目的是為了提高代碼的可重用性、代碼的可讀性和代碼的可靠性。
經(jīng)過匯總的23種設(shè)計(jì)模式它是總結(jié)了面向?qū)ο?/strong>設(shè)計(jì)當(dāng)中最有價(jià)值的經(jīng)驗(yàn)。對(duì)之前來講可能是對(duì)其中部分設(shè)計(jì)模式還是相對(duì)來說熟悉的但仔細(xì)琢磨還是會(huì)有些疑問,正好在目前相對(duì)來說有更多的業(yè)余時(shí)間,可以來一次重新學(xué)習(xí)設(shè)計(jì)模式!
本篇內(nèi)容關(guān)于原型模式。包含原型模式的設(shè)計(jì)與實(shí)現(xiàn)。
定義
原型模式是創(chuàng)建型模式的一種,其特點(diǎn)在于通過“復(fù)制”一個(gè)已經(jīng)存在的實(shí)例來返回新的實(shí)例,而不是新建實(shí)例。被復(fù)制的實(shí)例就是我們所稱的“原型”,這個(gè)原型是可定制的?!S基百科
原型模式多用于創(chuàng)建復(fù)雜的或者耗時(shí)的實(shí)例,因?yàn)檫@種情況下,復(fù)制一個(gè)已經(jīng)存在的實(shí)例使程序運(yùn)行更高效;或者創(chuàng)建值相等,只是命名不一樣的同類數(shù)據(jù)。
我們需要一系列對(duì)象,這些對(duì)象都是具有相同的特征,且它們都是獨(dú)立的。就像馬里奧里很多蘑菇人,它們是獨(dú)立的不會(huì)踩一個(gè)全部都掛掉。但它們是相同特征也就是深拷貝。
避免手動(dòng)去創(chuàng)建相同內(nèi)容的對(duì)象因?yàn)榈谝粡?fù)雜繁瑣效率不高,第二在外面真不一定能創(chuàng)建一模一樣的對(duì)象很多屬性是私有的且沒有公開方法方法。
對(duì)于一個(gè)馬里奧怪物類,我們可以去手動(dòng)創(chuàng)建蘑菇人對(duì)象,也可以創(chuàng)建烏龜對(duì)象。但我們?cè)賱?chuàng)建一個(gè)蘑菇人或者一百個(gè)就是通過原型模式的方法。只有定制化創(chuàng)建時(shí)去手動(dòng)設(shè)計(jì),之后再需要一模一樣的對(duì)象就通過原型方法獲取。達(dá)到定制一個(gè)批量復(fù)制。
自定義實(shí)現(xiàn)
先畫個(gè)結(jié)構(gòu)圖

首先是一個(gè)抽象接口,第二就是一個(gè)具體的原型類,去實(shí)現(xiàn)clone方法。具體原型類的實(shí)例通過使用clone方法就能產(chǎn)生一個(gè)一模一樣的新對(duì)象
interface?Prototype{
????Object?clone();
}
class?Guaiwu{
????private?String?name;
????private?String?icon;
????public?Guaiwu(String?name,String?icon){
????????this.name?=?name;
????????this.icon?=?icon;
????}
????public?Guaiwu(Guaiwu?obj){
????????this.name?=?obj.name;
????????this.icon?=?obj.icon;
????}
????@Override
????public?object?clone(){
????????return?new?Guaiwu(this);
????}
}
指定一個(gè)對(duì)象
Guaiwu?wugui?=?new?Guaiwu("小烏龜","烏龜樣子");
批量復(fù)制
Guaiwu?wugui1?=?wugui.clone();
Guaiwu?wugui2?=?wugui.clone();
Guaiwu?wugui3?=?wugui.clone();
...
以上呢就是一個(gè)簡(jiǎn)單的原型模式的一個(gè)自定義實(shí)現(xiàn)。

Object clone方法
不過其實(shí)在Java當(dāng)中已經(jīng)提供了clone方法,它是一個(gè)Object的本地方法

也就是說每個(gè)對(duì)象都已經(jīng)具備了clone方法,不需要像上面定義抽象標(biāo)準(zhǔn)再在每個(gè)具體原型類里面去實(shí)現(xiàn)。
class?Noodle{
????String?name;
????String?size;
????public?Noodle(String?name,String?size){
????????this.name?=?name;
????????this.size?=?name;
????}
}
class?Client{
????public?static?void?main(String[]?args){
????????//?指定原型對(duì)象
????????Noodle?noodle?=?new?Noodle("長(zhǎng)壽面","超大份");
????????//?批量復(fù)制
????????Noodle?n1?=?noodle.clone();
????????Noodle?n2?=?noodle.clone();
????????//?比較
????????System.out.println(n1);
????????System.out.println(noodle?==?n1);
????????System.out.println(n1?==?n2);
????}
}
結(jié)構(gòu)也是符合預(yù)期的,確實(shí)n1與n2的內(nèi)容是和noodle一樣。且它三個(gè)確實(shí)是存在三個(gè)對(duì)象而不是一個(gè)。
但這個(gè)Object提供的clone對(duì)于我們的需要來說其實(shí)是存在問題的,它雖然是新建了對(duì)象和我們上面自己實(shí)現(xiàn)的clone()一樣,但新建對(duì)象的屬性內(nèi)容是直接賦值過去。也就是說如果屬性是一個(gè)非基本類型。那么clone出來的對(duì)象里的這個(gè)屬性和原型的對(duì)象里的這個(gè)屬性是指向同一個(gè)對(duì)象。也就是說它是淺拷貝。

Object的這個(gè)clone,其實(shí)就是和上面完全自定義寫的那個(gè)一樣,雖然是新建了對(duì)象n1,但n1的屬性內(nèi)容只是把noodle的內(nèi)容挨個(gè)的賦值進(jìn)去。導(dǎo)致非基本類型是引用相同對(duì)象而不是新對(duì)象。

我們需要的是完完全全的深拷貝

能怎么辦,重寫實(shí)現(xiàn),針對(duì)當(dāng)前原型類的屬性,完善clone邏輯
//?佐料
class?Spice?implements?Cloneable{
????String?name;
????String?type;
????...
}
//?面條(原型類)
class?Noodle?implements?Cloneable{
????String?name;
????String?size;
????Spice?spice;
????
????@Override
????public?Noodle?clone(){
????????//?先調(diào)用原來的clone,也就是得到屬性都是直接復(fù)制到新對(duì)象
????????Noode?noodle?=?(Noodle)super.clone();
????????//?再把spice復(fù)制個(gè)新對(duì)象,設(shè)置進(jìn)去
????????noodle.spice?=?this.spice.clone();
????????return?noodle;
????}
}

這樣就完成了clone方法對(duì)當(dāng)前Noodle類型對(duì)象是完全拷貝
要注意的是訪問clone方法是protected因此只能在java.lang訪問。因此在外部非子類訪問需要重寫(開頭的代碼沒有寫所以提示下),且重寫必須得實(shí)現(xiàn)Cloneable接口,說明如下:
這樣寫出來就比較復(fù)雜,為了進(jìn)行深拷貝里面有復(fù)雜類型的屬性,而屬性里面可能還牽連很多其他的非基本類型。導(dǎo)致在原型對(duì)象類里面不停的嵌套去實(shí)現(xiàn)直到將所有屬性的值都創(chuàng)建新對(duì)象。對(duì)于復(fù)雜類型復(fù)雜的組合依賴關(guān)系,上面的clone方法的實(shí)現(xiàn)就會(huì)復(fù)雜很多,要遞歸到只有基本類型為止,從最底層一個(gè)一個(gè)對(duì)象進(jìn)行創(chuàng)建
序列化
不過還好有序列化,可以利用序列化的方式來達(dá)到深拷貝
@Override
public?Object?clone()?{
????try{
????????//?寫出
????????ByteArrayOutputStream?aos?=?new?ByteArrayOutputStream();
????????ObjectOutputStream?oos?=?new?ObjectOutputStream(aos);
????????oos.writeObject(this);
????????//?讀取
????????ByteArrayInputStream?ais?=?new?ByteArrayInputStream(aos.toByteArray());
????????ObjectInputStream?ois?=?new?ObjectInputStream(ais);
????????return?ois.readObject();
????}catch?(Exception?e){
????????e.printStackTrace();
????}
????return?null;
}
最終還是可以一步到位的,里面的嵌套的非基本類型的屬性,也都是新對(duì)象。使用序列化的方式就避免了手動(dòng)去遞歸挖掘所有關(guān)聯(lián)非基本類型的屬性,進(jìn)行新建拷貝內(nèi)容再賦值的過程。
但怎么說還是得改寫所有關(guān)聯(lián)的屬性的類以及下面不停嵌套的類,都要去實(shí)現(xiàn)序列化接口。只是對(duì)于clone方法的重寫可以一步到位。
總結(jié)
相對(duì)之前來說這個(gè)原型模式思想是比較簡(jiǎn)單的,它可能會(huì)用在需要復(fù)用很多對(duì)象,且要求保證獨(dú)立安全。優(yōu)點(diǎn)在于:第一客戶代碼可以直接通過模板對(duì)象調(diào)用clone就能得到新對(duì)象,免去對(duì)使用類的了解程度的門檻。第二有些情況下就是需要進(jìn)行復(fù)制,在這樣的情況下也就比直接創(chuàng)建效率要高。缺點(diǎn)也就是在于類的成員屬性的類型的復(fù)雜程度,這時(shí)候復(fù)制的邏輯就比較復(fù)雜,或者說需要很注意。但無論怎么說其實(shí)都要去修改代碼,不符合開閉原則。還得看具體場(chǎng)景吧實(shí)際使用的不多。


往期推薦
3分鐘學(xué)設(shè)計(jì)模式(創(chuàng)建型):4、建造者模式
3分鐘學(xué)設(shè)計(jì)模式(創(chuàng)建型)3、抽象工廠模式
3分鐘學(xué)設(shè)計(jì)模式(創(chuàng)建型):2、工廠方法模式
3分鐘學(xué)設(shè)計(jì)模式(創(chuàng)建型):1、單例模式

掃碼二維碼
獲取更多精彩
Jasper小筆記

