原型模式
介紹
在許多面向?qū)ο蟮膽?yīng)用程序中塘匣,有些對(duì)象的創(chuàng)建代價(jià)過大或者過于復(fù)雜。要是可以重建相同的對(duì)象并作輕微的改動(dòng)巷帝,事情就會(huì)容易很多忌卤。典型的例子就是復(fù)制組合結(jié)構(gòu)(比如樹型結(jié)構(gòu))。從零構(gòu)建一個(gè)樹型組合體非常困難楞泼,我們可以通過輕微改動(dòng)重用已有的對(duì)象驰徊,以適應(yīng)程序中的特定狀況。
在使用原型模式時(shí)堕阔,我們需要首先創(chuàng)建一個(gè)原型對(duì)象棍厂,再通過復(fù)制這個(gè)原型對(duì)象來創(chuàng)建更多同類型的對(duì)象。
定義
使用原型實(shí)例指定創(chuàng)建對(duì)象的種類超陆,并且通過拷貝這些原型創(chuàng)建新的對(duì)象牺弹。原型模式是一種對(duì)象創(chuàng)建型模式。
原型模式的工作原理很簡(jiǎn)單:將一個(gè)原型對(duì)象傳給那個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象时呀,這個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象通過請(qǐng)求原型對(duì)象拷貝自己來實(shí)現(xiàn)創(chuàng)建過程张漂。由于在軟件系統(tǒng)中我們經(jīng)常會(huì)遇到需要?jiǎng)?chuàng)建多個(gè)相同或者相似對(duì)象的情
需要注意的是通過克隆方法所創(chuàng)建的對(duì)象是全新的對(duì)象,它們?cè)趦?nèi)存中擁有新的地址谨娜,通常對(duì)克隆所產(chǎn)生的對(duì)象進(jìn)行修改對(duì)原型對(duì)象不會(huì)造成任何影響航攒,每一個(gè)克隆對(duì)象都是相互獨(dú)立的。通過不同的方式修改可以得到一系列相似但不完全相同的對(duì)象趴梢。
UML類圖
Prototype聲明了復(fù)制自身的接口漠畜,作為prototype的子類,ConcretePrototype實(shí)現(xiàn)了自身的clone操作坞靶。
角色介紹
- Prototype(抽象原型類):它是聲明克隆方法的接口憔狞,是所有具體原型類的公共父類,可以是抽象類也可以是接口彰阴,甚至還可以是具體實(shí)現(xiàn)類躯喇。
- ConcretePrototype(具體原型類):它實(shí)現(xiàn)在抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個(gè)克隆對(duì)象。
- Client(客戶類):讓一個(gè)原型對(duì)象克隆自身從而創(chuàng)建一個(gè)新的對(duì)象廉丽,在客戶類中只需要直接實(shí)例化或通過工廠方法等方式創(chuàng)建一個(gè)原型對(duì)象倦微,再通過調(diào)用該對(duì)象的克隆方法即可得到多個(gè)相同的對(duì)象。由于客戶類針對(duì)抽象原型類Prototype編程正压,因此用戶可以根據(jù)需要選擇具體原型類欣福,系統(tǒng)具有較好的可擴(kuò)展性,增加或更換具體原型類都很方便焦履。
實(shí)現(xiàn)
class ConcretePrototype implements Prototype
{
private String attr; //成員屬性
public void setAttr(String attr)
{
this.attr = attr;
}
public String getAttr()
{
return this.attr;
}
public Prototype clone() //克隆方法
{
Prototype prototype = new ConcretePrototype(); //創(chuàng)建新對(duì)象
prototype.setAttr(this.attr);
return prototype;
}
}
客戶端調(diào)用
Prototype obj1 = new ConcretePrototype();
obj1.setAttr("Sunny");
Prototype obj2 = obj1.clone();
克隆
淺克隆
在淺克隆中拓劝,如果原型對(duì)象的成員變量是值類型,將復(fù)制一份給克隆對(duì)象嘉裤;如果原型對(duì)象的成員變量是引用類型郑临,則將引用對(duì)象的地址復(fù)制一份給克隆對(duì)象,也就是說原型對(duì)象和克隆對(duì)象的成員變量指向相同的內(nèi)存地址屑宠。簡(jiǎn)單來說厢洞,在淺克隆中,當(dāng)對(duì)象被復(fù)制時(shí)只復(fù)制它本身和其中包含的值類型的成員變量典奉,而引用類型的成員對(duì)象并沒有復(fù)制躺翻。
在Java語言中,通過覆蓋Object類的clone()方法可以實(shí)現(xiàn)淺克隆卫玖。
c中的淺拷貝就是對(duì)內(nèi)存地址的復(fù)制公你,讓目標(biāo)對(duì)象指針和源對(duì)象指向同一片內(nèi)存空間。如:
char *str = (char*)malloc(100);
char* str2 = str;
淺拷貝只是對(duì)對(duì)象的簡(jiǎn)單拷貝假瞬,讓幾個(gè)對(duì)象共用同一片內(nèi)存陕靠,當(dāng)內(nèi)存銷毀時(shí),指向這片內(nèi)存的幾個(gè)指針需要重新定義才可以使用脱茉,要不然成為野指針剪芥。
深克隆
在深克隆中,無論原型對(duì)象的成員變量是值類型還是引用類型芦劣,都將復(fù)制一份給克隆對(duì)象,深克隆將原型對(duì)象的所有引用對(duì)象也復(fù)制一份給克隆對(duì)象说榆。簡(jiǎn)單來說虚吟,在深克隆中,除了對(duì)象本身被復(fù)制外签财,對(duì)象所包含的所有成員變量也將復(fù)制串慰。
在Java語言中,如果需要實(shí)現(xiàn)深克隆唱蒸,可以通過序列化(Serialization)等方式來實(shí)現(xiàn)邦鲫。序列化就是將對(duì)象寫到流的過程,寫到流中的對(duì)象是原有對(duì)象的一個(gè)拷貝,而原對(duì)象仍然存在于內(nèi)存中庆捺。通過序列化實(shí)現(xiàn)的拷貝不僅可以復(fù)制對(duì)象本身古今,而且可以復(fù)制其引用的成員對(duì)象,因此通過序列化將對(duì)象寫到一個(gè)流中滔以,再?gòu)牧骼飳⑵渥x出來捉腥,可以實(shí)現(xiàn)深克隆。需要注意的是能夠?qū)崿F(xiàn)序列化的對(duì)象其類必須實(shí)現(xiàn)Serializable接口你画,否則無法實(shí)現(xiàn)序列化操作抵碟。
c中的深拷貝是指拷貝對(duì)象的內(nèi)容也重新分配一塊地址,兩個(gè)對(duì)象也互不影響坏匪、互不干涉拟逮。如:
char *str = (char*)malloc(100);
char *str2 = (char*)malloc(100);
memcpy(str2, str, sizeof(char)* 100);
Cocoa中對(duì)象的復(fù)制
Cocoa為NSObject的派生類提供了實(shí)現(xiàn)深復(fù)制的協(xié)議。NSObject的子類需要實(shí)現(xiàn)NSCopying協(xié)議及其方法-(id)copyWithZone:(NSZone *)zone适滓。NSObject有一個(gè)實(shí)例方法copy敦迄,默認(rèn)的copy方法內(nèi)部調(diào)用[self copyWithZone:nil]。對(duì)于采納了NSCopying協(xié)議的子類粒竖,必須要實(shí)現(xiàn)這個(gè)copyWithZone方法颅崩,否則會(huì)發(fā)生異常.
iOS中普通對(duì)象的拷貝
- 若沒有實(shí)現(xiàn)過copyWithZone, 則可直接使用alloc和init創(chuàng)建新對(duì)象;
- 若該類繼承的父類實(shí)現(xiàn)了copyWithZone方法蕊苗,則需overwrite該方法沿后,先調(diào)用父類的該方法,再對(duì)新增的變量賦值朽砰;
- 若為immutable對(duì)象尖滚,則直接retain+1,返回原始對(duì)象;(如同NSString)
demo
@implementation TestObj
-(id)copyWithZone:(NSZone *)zone
{
Tire *copy = [[[self class] allocWithZone:zone] init];
return copy;
}
@end
子類實(shí)現(xiàn)copying協(xié)議瞧柔,先調(diào)用父類
-(id)copyWithZone: (NSZone *) zone
{
AllWeatherRadial *tireCopy;
tireCopy = [super copyWithZone : zone];
tireCopy.rainHandling = rainHandling;
tireCopy.snowHandling = snowHandling;
return tireCopy;
}
NSMutableCopying
對(duì)于mutable對(duì)象漆弄,實(shí)現(xiàn)的是NSMutableCopying協(xié)議
如果該類的父類實(shí)現(xiàn)了mutableCopyWithZone:方法, 則需要重寫該方法造锅,先調(diào)用父類的該方法撼唾,再對(duì)新增的變量賦值;
iOS中集合對(duì)象的淺拷貝
NSArray *shallowCopyArray = [someArray copyWithZone:nil];
// or
NSDictionary *shallowCopyDict = [[NSDictioary alloc] initWitheDictionary:someDictioanry copyItems: NO];
iOS中集合對(duì)象的深拷貝
NSArray *deepCopyArray = [NSArray alloc] initWitheArray: someArray copyItems: YES]; // 只深拷貝了一層哥蔚,如果數(shù)組的元素也為 集合類型倒谷, 則無法完全拷貝
// or
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: oldArray]]; // 完全拷貝
iOS中NSString、NSMutableString對(duì)象
NSString對(duì)象的retain非常特殊糙箍。
總結(jié)起來是:
@式:
NSString *s = @"t";
NSLog(@"s:%lx",[s retainCount]);//輸出值為0xffffffffffffffff(UINT_MAX, 2147483647)
NSLog(@"s:%ld",[s retainCount]);//輸出值-1,由于0xffffffffffffffff補(bǔ)碼表示的值為-1
s指向的是字符串常量(類似于c語言中的靜態(tài)區(qū)存儲(chǔ))渤愁,系統(tǒng)不做引用計(jì)數(shù),任何retain\release操作深夯,其retainCount值都為UINT_MAX抖格。
stringWithFormat:
NSString *s = [NSString stringWithFormat:@"%s", "test"];
NSLog(@"s:%d",[s retainCount]); //輸出值為1
系統(tǒng)會(huì)正常使用引用計(jì)數(shù),和普通對(duì)象一樣;
stringWithString:
取決于后面的string對(duì)象雹拄;
NSString *s1 = [NSString stringWithString:@"test"];
NSLog(@"s1:%d",[s1 retainCount]); // 2147483647
如“test”為常量收奔,則s1也指向常量,retain為UINT_MAX;
NSString *s2 = [NSString stringWithString:[NSString stringWithFormat:@"test,%d",1]];
NSLog(@"s2:%d",[s2 retainCount]); // 2
如先用stringWithFormat生成了一個(gè)變量办桨,retain為1筹淫,再用stringWithString,retain增為2.
NSMutableString
NSMutableString* myStr3 = [NSMutableString stringWithString:@"string 3"]; // 1
使用stringWithString呢撞,正常計(jì)數(shù)损姜。
因此 這里只對(duì)使用stringWithFormat式創(chuàng)建的對(duì)象的copy/mutblecopy行為進(jìn)行研究。
T* source = [T stringWithFormat: @"test"];
T* dest = [source copy/mutablecopy];
使用copy殊霞,無論source\dest對(duì)象是否為mutable摧阅,都會(huì)退化為immutable;且執(zhí)行的是淺拷貝绷蹲,指向同一個(gè)老的對(duì)象棒卷, retain數(shù)+1;
使用mutablecopy祝钢,只有在source和dest都為NSMutableString時(shí)比规,可正常使用;且執(zhí)行的是深拷貝拦英,生成新對(duì)象蜒什,retain數(shù)為1。
iOS拷貝小總計(jì)
總結(jié)
優(yōu)點(diǎn)
- 當(dāng)創(chuàng)建新的對(duì)象實(shí)例較為復(fù)雜時(shí)疤估,使用原型模式可以簡(jiǎn)化對(duì)象的創(chuàng)建過程灾常,通過復(fù)制一個(gè)已有實(shí)例可以提高新實(shí)例的創(chuàng)建效率。
- 擴(kuò)展性較好铃拇,由于在原型模式中提供了抽象原型類钞瀑,在客戶端可以針對(duì)抽象原型類進(jìn)行編程,而將具體原型類寫在配置文件中慷荔,增加或減少產(chǎn)品類對(duì)原有系統(tǒng)都沒有任何影響雕什。
- 原型模式提供了簡(jiǎn)化的創(chuàng)建結(jié)構(gòu),工廠方法模式常常需要有一個(gè)與產(chǎn)品類等級(jí)結(jié)構(gòu)相同的工廠等級(jí)結(jié)構(gòu)显晶,而原型模式就不需要這樣贷岸,原型模式中產(chǎn)品的復(fù)制是通過封裝在原型類中的克隆方法實(shí)現(xiàn)的,無須專門的工廠類來創(chuàng)建產(chǎn)品吧碾。
- 可以使用深克隆的方式保存對(duì)象的狀態(tài)凰盔,使用原型模式將對(duì)象復(fù)制一份并將其狀態(tài)保存起來墓卦,以便在需要的時(shí)候使用(如恢復(fù)到某一歷史狀態(tài))倦春,可輔助實(shí)現(xiàn)撤銷操作。
缺點(diǎn)
- 需要為每一個(gè)類配備一個(gè)克隆方法,而且該克隆方法位于一個(gè)類的內(nèi)部睁本,當(dāng)對(duì)已有的類進(jìn)行改造時(shí)尿庐,需要修改源代碼,違背了“開閉原則”呢堰。
- 在實(shí)現(xiàn)深克隆時(shí)需要編寫較為復(fù)雜的代碼抄瑟,而且當(dāng)對(duì)象之間存在多重的嵌套引用時(shí),為了實(shí)現(xiàn)深克隆枉疼,每一層對(duì)象對(duì)應(yīng)的類都必須支持深克隆皮假,實(shí)現(xiàn)起來可能會(huì)比較麻煩。
適用場(chǎng)景
- 創(chuàng)建新對(duì)象成本較大(如初始化需要占用較長(zhǎng)的時(shí)間骂维,占用太多的CPU資源或網(wǎng)絡(luò)資源)惹资,新的對(duì)象可以通過原型模式對(duì)已有對(duì)象進(jìn)行復(fù)制來獲得,如果是相似對(duì)象航闺,則可以對(duì)其成員變量稍作修改褪测。
- 如果系統(tǒng)要保存對(duì)象的狀態(tài),而對(duì)象的狀態(tài)變化很小潦刃,或者對(duì)象本身占用內(nèi)存較少時(shí)侮措,可以使用原型模式配合備忘錄模式來實(shí)現(xiàn)。
- 需要避免使用分層次的工廠類來創(chuàng)建分層次的對(duì)象乖杠,并且類的實(shí)例對(duì)象只有一個(gè)或很少的幾個(gè)組合狀態(tài)分扎,通過復(fù)制原型對(duì)象得到新實(shí)例可能比使用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例更加方便。