原型模式

原型模式的目的就是通过复制的手段得到一个新的对象。

个人认为这种模式最大的好处是省掉了“工厂”这层概念,因为工厂通常给人的印象是某种初始化动作,而拷贝则是建立在运行时已经生成的对象的基础之上的。

一个更大的好处是可以让拷贝操作作用在聚合根之上,可以一次性拷贝一套复杂的对象集合,对外则屏蔽了赋值过程的细节。

拷贝分为深拷贝和浅拷贝两种,浅拷贝就是建立一个新指针指向同一个对象,即同一个对象多份引用,深拷贝则是完全复制目标对象,生成一个新的对象二进制数据,并用一个新指针指向新的对象。对于浅拷贝,我觉得没什么好说的。

对于深拷贝,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就可以了,这样就能保证聚合根的拷贝操作与被聚合类的拷贝操作不发生耦合。

原型模式

上一篇:如何从servlet中获取客户端ip并转换ip为long值


下一篇:UML—交互图