原型模式是非常簡單的一種設(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)存不變. 在我們設(shè)計(jì)系統(tǒng)時(shí), 有時(shí)一些對象需要根據(jù)用戶操作完成拷貝備份等操作, 這時(shí)候, 如果再去按照原來的方法初始化一遍對象就會帶來一些不便和問題:
- 該對象的某些屬性是在用戶操作過程中產(chǎn)生的, 不能夠僅憑一個(gè)initXXX方法賦值;
- 常規(guī)賦值太過麻煩, 而且破壞封裝.
這時(shí)候原型模式的優(yōu)勢便體現(xiàn)出來了??偨Y(jié)下原型模式的使用情況:
- 需要?jiǎng)?chuàng)建的對象應(yīng)獨(dú)立于其類型與創(chuàng)建方式。也就是說我們想要的對象并不能夠直接通過初始化函數(shù)來創(chuàng)建出來,其創(chuàng)建過程不具有普遍性且復(fù)雜。
- 要實(shí)例化類是在運(yùn)行時(shí)決定的。在編寫代碼的時(shí)候并不知道哪種對象會被創(chuàng)建出來,其內(nèi)部的結(jié)構(gòu)如何復(fù)雜(例如:復(fù)雜程度取決于用戶的操作)。
- 不想要與產(chǎn)品層次相對應(yīng)的工廠層次。不通過工廠方法或者抽象工廠來控制產(chǎn)品的創(chuàng)建過程,想要直接復(fù)制對象
- 不同類的實(shí)例間的差異僅是狀態(tài)的若干組合。因此復(fù)制相應(yīng)數(shù)量的原型比手工實(shí)例化更加方便。
- 類不容易創(chuàng)建,比如每個(gè)組件可把其他組件作為子節(jié)點(diǎn)的組合對象。復(fù)制已有的組合對象并對副本進(jìn)行修改會更加容易。如果內(nèi)部結(jié)構(gòu)復(fù)雜,不容易重現(xiàn)。
常見的使用場景 : 結(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會根據(jù)你想要開辟的內(nèi)存大小來分配內(nèi)存, 提高內(nèi)存管理. 然而官方的Programming with ARC Release Note也指出, 目前的runtime系統(tǒng)忽略了區(qū)域的概念,因?yàn)楸旧淼膬?nèi)存管理已經(jīng)非常有效率,使用Zone反而會降低內(nèi)存使用,訪問效率, 增加源代碼復(fù)雜度等.所以一般不使用NSZone, 而在這個(gè)例子中, 雖說使用了allocWithZone的方法, 但是我們進(jìn)去看源代碼則會發(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.