原型模式是非常簡(jiǎn)單的一種設(shè)計(jì)模式, 在多數(shù)情況下可被理解為一種深復(fù)制的行為穗熬。在Objective-C中使用原型模式, 首先要遵循NSCoping協(xié)議(OC中一些內(nèi)置類遵循該協(xié)議, 例如NSArray, NSMutableArray等)瞬雹。剛才我們提到了深復(fù)制, 一圖以蔽之:
深復(fù)制就是開辟新內(nèi)存實(shí)現(xiàn)真正的內(nèi)存復(fù)制, 淺復(fù)制, 只復(fù)制指針, 堆內(nèi)存不變. 在我們?cè)O(shè)計(jì)系統(tǒng)時(shí), 有時(shí)一些對(duì)象需要根據(jù)用戶操作完成拷貝備份等操作, 這時(shí)候, 如果再去按照原來的方法初始化一遍對(duì)象就會(huì)帶來一些不便和問題:
- 該對(duì)象的某些屬性是在用戶操作過程中產(chǎn)生的, 不能夠僅憑一個(gè)initXXX方法賦值;
- 常規(guī)賦值太過麻煩, 而且破壞封裝.
這時(shí)候原型模式的優(yōu)勢(shì)便體現(xiàn)出來了惕艳』嬷ぃ總結(jié)下原型模式的使用情況:
- 需要?jiǎng)?chuàng)建的對(duì)象應(yīng)獨(dú)立于其類型與創(chuàng)建方式仅炊。也就是說我們想要的對(duì)象并不能夠直接通過初始化函數(shù)來創(chuàng)建出來弄诲,其創(chuàng)建過程不具有普遍性且復(fù)雜寓涨。
- 要實(shí)例化類是在運(yùn)行時(shí)決定的盯串。在編寫代碼的時(shí)候并不知道哪種對(duì)象會(huì)被創(chuàng)建出來,其內(nèi)部的結(jié)構(gòu)如何復(fù)雜(例如:復(fù)雜程度取決于用戶的操作)戒良。
- 不想要與產(chǎn)品層次相對(duì)應(yīng)的工廠層次体捏。不通過工廠方法或者抽象工廠來控制產(chǎn)品的創(chuàng)建過程,想要直接復(fù)制對(duì)象
- 不同類的實(shí)例間的差異僅是狀態(tài)的若干組合蔬墩。因此復(fù)制相應(yīng)數(shù)量的原型比手工實(shí)例化更加方便译打。
- 類不容易創(chuàng)建,比如每個(gè)組件可把其他組件作為子節(jié)點(diǎn)的組合對(duì)象拇颅。復(fù)制已有的組合對(duì)象并對(duì)副本進(jìn)行修改會(huì)更加容易奏司。如果內(nèi)部結(jié)構(gòu)復(fù)雜,不容易重現(xiàn)樟插。
常見的使用場(chǎng)景 : 結(jié)構(gòu)復(fù)雜的類型, 例如《編程之道》中提及的Mark類型韵洋; 或者是相似類, 結(jié)構(gòu)一致, 但是屬性不同.
Demo
首先創(chuàng)建一個(gè)Player類, 擁有2個(gè)屬性highestLevel和currentLevel, 同時(shí)提供2個(gè)public方法修改這2個(gè)屬性. 代碼如下:
@interface Player : NSObject <NSCopying>
/**
* update player's current level during game
*
* @param level
*/
- (void)updateCurrentLevel:(NSInteger)level;
/**
* update player's highest level during game
*
* @param level
*/
- (void)updateHighestLevel:(NSInteger)level;
@end
最為關(guān)鍵的是Player需要實(shí)現(xiàn)NSCopying協(xié)議:
#pragma mark - Override
- (instancetype)copyWithZone:(NSZone *)zone
{
Player *copyPlayer = [[[self class] allocWithZone:zone] init];
copyPlayer.highestLevel = self.highestLevel;
copyPlayer.currentLevel = self.currentLevel;
return copyPlayer;
}
這里大家看到NSZone類型, 這是個(gè)什么類型呢? 其實(shí)它是一個(gè)結(jié)構(gòu)體, 是為了防止內(nèi)存碎片化而引入的一個(gè)結(jié)構(gòu). NSZone會(huì)根據(jù)你想要開辟的內(nèi)存大小來分配內(nèi)存, 提高內(nèi)存管理. 然而官方的Programming with ARC Release Note也指出, 目前的runtime系統(tǒng)忽略了區(qū)域的概念黄锤,因?yàn)楸旧淼膬?nèi)存管理已經(jīng)非常有效率搪缨,使用Zone反而會(huì)降低內(nèi)存使用,訪問效率, 增加源代碼復(fù)雜度等.所以一般不使用NSZone鸵熟, 而在這個(gè)例子中副编, 雖說使用了allocWithZone的方法, 但是我們進(jìn)去看源代碼則會(huì)發(fā)現(xiàn): Apple其實(shí)還是用一般的初始化方法代替了原來的Zone開辟:
#pragma mark - Override
- (instancetype)copyWithZone:(NSZone *)zone
+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object
initializers instead");
原型設(shè)計(jì)模式基本就是這些, 當(dāng)然我們的Player類可以變成一個(gè)接口, 讓子類去實(shí)現(xiàn), 更好的體現(xiàn)面向接口編程.
結(jié)果
2015-09-18 21:30:32.072 DP_Prototype[1173:280693] <Player: 0x14d513f60>
2015-09-18 21:30:32.073 DP_Prototype[1173:280693] <Player: 0x14d5337e0>
在其他文件調(diào)用copy方法, 即可看到系統(tǒng)為我們新開辟的一塊內(nèi)存, 引用計(jì)數(shù)為1.