原型模式的目的就是通过复制的手段得到一个新的对象。
个人认为这种模式最大的好处是省掉了“工厂”这层概念,因为工厂通常给人的印象是某种初始化动作,而拷贝则是建立在运行时已经生成的对象的基础之上的。
一个更大的好处是可以让拷贝操作作用在聚合根之上,可以一次性拷贝一套复杂的对象集合,对外则屏蔽了赋值过程的细节。
拷贝分为深拷贝和浅拷贝两种,浅拷贝就是建立一个新指针指向同一个对象,即同一个对象多份引用,深拷贝则是完全复制目标对象,生成一个新的对象二进制数据,并用一个新指针指向新的对象。对于浅拷贝,我觉得没什么好说的。
对于深拷贝,java/C#当中都提供了克隆接口(我估计很多人都没直接用过它们),OC则提供了NSCopying协议,OC的很多类(比如词典类)都已经实现了这个协议。
深拷贝要解决的问题分以下几种:普通类的拷贝,子类的拷贝,聚合根的拷贝。
对于一个普通类Book:
1 #import <Foundation/Foundation.h> 2 3 @interface Book : NSObject <NSCopying> 4 @property (nonatomic, copy) NSString *name; 5 - (id)copyWithZone:(NSZone *)zone; 6 @end
1 #import "Book.h" 2 3 @implementation Book 4 5 - (id)copyWithZone:(NSZone *)zone{ 6 Book *copy = [[Book allocWithZone:zone]init]; 7 copy.name = self.name; 8 return copy; 9 } 10 11 @end
1 #import <Foundation/Foundation.h> 2 #import "Book.h" 3 4 int main(int argc, const char * argv[]) 5 { 6 @autoreleasepool { 7 Book *book = [[Book alloc]init]; 8 book.name = @"test"; 9 10 Book *book2 = [book copy]; 11 12 NSLog(@"%@", book2.name); //test 13 } 14 return 0; 15 }
实现拷贝的过程只要实现NSCopying协议当中的方法,并在其中创建一个新的对象就可以了。
当Book类存在子类时,我们希望子类也能够支持拷贝:
1 #import "Book.h" 2 3 @interface ProgramingBook : Book <NSCopying> 4 @property (nonatomic, copy) NSString *language; 5 - (id)copyWithZone:(NSZone *)zone; 6 @end
1 #import "ProgramingBook.h" 2 3 @implementation ProgramingBook 4 5 - (id)copyWithZone:(NSZone *)zone{ 6 ProgramingBook *copy = [super copyWithZone:zone]; 7 copy.language = self.language; 8 return copy; 9 } 10 11 @end
这里调用了父类的copyWithZone:方法,因为从对象的内存结构来看,父类的拷贝工作需要由父类自己完成,这点无可厚非。
问题是当调用拷贝方法时:
1 #import <Foundation/Foundation.h> 2 #import "Book.h" 3 #import "ProgramingBook.h" 4 int main(int argc, const char * argv[]) 5 { 6 @autoreleasepool { 7 ProgramingBook *book = [[ProgramingBook alloc]init]; 8 book.name = @"test"; 9 book.language = @"java"; 10 11 ProgramingBook *book2 = [book copy]; 12 13 NSLog(@"%@", book2.name); 14 NSLog(@"%@", book2.language); 15 } 16 return 0; 17 }
却会报错,出错的代码是子类拷贝方法中的
1 copy.language = self.language;
错误信息是熟悉的unrecognized selector sent to instance 0x100101020 找不到方法。
仔细分析会发现是找不到self对language参数的getter方法,那么就要怀疑这个self到底是什么。
由于子类的拷贝方法调用了父类的拷贝方法,而父类的拷贝方法当中有这样一句话:
1 Book *copy = [[Book allocWithZone:zone]init];
显然,这里分配了一个Book类型的对象,那么self就表示Book类型,它是不存在language的getter方法的。
解决方法是把父类拷贝方法做改造:
1 #import "Book.h" 2 3 @implementation Book 4 5 - (id)copyWithZone:(NSZone *)zone{ 6 Book *copy = [[[self class] allocWithZone:zone]init]; 7 copy.name = self.name; 8 return copy; 9 } 10 11 @end
这样就能正确地初始化子类对象的内存空间、以及生成正确的getter方法了。
第三种情况就是对聚合根的拷贝,这里举一个最简单的例子,书当中聚合了一个叫做Pages的集合,其中含有一个页数属性,其他的属性这里略去:
让这个类也实现NSCopying协议,因为在聚合根Book当中,也是要对Pages类进行深拷贝的,而它的深拷贝行为应该是自治的:
1 #import <Foundation/Foundation.h> 2 3 @interface Pages : NSObject <NSCopying> 4 @property (nonatomic, assign) NSInteger number; 5 - (id)copyWithZone:(NSZone *)zone; 6 @end
1 #import "Pages.h" 2 3 @implementation Pages 4 5 - (id)copyWithZone:(NSZone *)zone { 6 Pages *copy = [[[self class] allocWithZone:zone]init]; 7 copy.number = self.number; 8 return copy; 9 } 10 11 @end
聚合根类Book定义改为:
1 #import <Foundation/Foundation.h> 2 3 @class Pages; 4 5 @interface Book : NSObject <NSCopying> 6 @property (nonatomic, copy) NSString *name; 7 @property (nonatomic, strong) Pages *pages; 8 - (id)copyWithZone:(NSZone *)zone; 9 @end
1 #import "Book.h" 2 #import "Pages.h" 3 4 @implementation Book 5 6 - (id)copyWithZone:(NSZone *)zone{ 7 Book *copy = [[[self class] allocWithZone:zone]init]; 8 copy.name = self.name; 9 copy.pages = [self.pages copy]; 10 return copy; 11 } 12 13 @end
只要在其中对pages变量进行copy就可以了,这样就能保证聚合根的拷贝操作与被聚合类的拷贝操作不发生耦合。