原型模式(Prototype Pattern): 使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
原型模式主要包含如下两个角色:
下面通过类图来看一下各个成员之间的关系:
原型模式类图
需要注意的是,这里面的clone()
方法返回的是被复制出来的实例对象。
模拟一份校招的简历,简历里面有人名,性别,年龄以及学历相关的信息。这里面学历相关的信息又包含学校名称,专业,开始和截止年限的信息。
这里的学历相关信息可以使用单独一个对象来做,因此整体的简历对象的结构可以是:
简历对象:
而且因为对于同一学校同一届的同一专业的毕业生来说,学历对象中的信息是相同的,这时候如果需要大量生成这些毕业生的简历的话比较适合使用原型模式。
首先定义学历对象:
//================== UniversityInfo.h ==================
@interface UniversityInfo : NSObject<NSCopying>
@property (nonatomic, copy) NSString *universityName;
@property (nonatomic, copy) NSString *startYear;
@property (nonatomic, copy) NSString *endYear;
@property (nonatomic, copy) NSString *major;
- (id)copyWithZone:(NSZone *)zone;
@end
//================== UniversityInfo.m ==================
@implementation UniversityInfo
- (id)copyWithZone:(NSZone *)zone
{
UniversityInfo *infoCopy = [[[self class] allocWithZone:zone] init];
[infoCopy setUniversityName:[_universityName mutableCopy]];
[infoCopy setStartYear:[_startYear mutableCopy]];
[infoCopy setEndYear:[_endYear mutableCopy]];
[infoCopy setMajor:[_major mutableCopy]];
return infoCopy;
}
@end
因为学历对象是支持复制的,因此需要遵从
<NSCopying>
协议并实现copyWithZone:
方法。而且支持的是深复制,所以在复制NSString的过程中需要使用mutableCopy
来实现。
接着我们看一下简历对象:
//================== Resume.h ==================
#import "UniversityInfo.h"
@interface Resume : NSObject<NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *gender;
@property (nonatomic, copy) NSString *age;
@property (nonatomic, strong) UniversityInfo *universityInfo;
@end
//================== Resume.m ==================
@implementation Resume
- (id)copyWithZone:(NSZone *)zone
{
Resume *resumeCopy = [[[self class] allocWithZone:zone] init];
[resumeCopy setName:[_name mutableCopy]];
[resumeCopy setGender:[_gender mutableCopy]];
[resumeCopy setAge:[_age mutableCopy]];
[resumeCopy setUniversityInfo:[_universityInfo copy]];
return resumeCopy;
}
@end
同样地,简历对象也需要遵从
<NSCopying>
协议并实现copyWithZone:
方法。
最后我们看一下复制的效果有没有达到我们的预期(被复制对象和复制对象的地址和它们所有的属性对象的地址都不相同)
//================== Using by client ==================
//resume for LiLei
Resume *resume = [[Resume alloc] init];
resume.name = @"LiLei";
resume.gender = @"male";
resume.age = @"24";
UniversityInfo *info = [[UniversityInfo alloc] init];
info.universityName = @"X";
info.startYear = @"2014";
info.endYear = @"2018";
info.major = @"CS";
resume.universityInfo = info;
//resume_copy for HanMeiMei
Resume *resume_copy = [resume copy];
NSLog(@"\n\n\n======== original resume ======== %@\n\n\n======== copy resume ======== %@",resume,resume_copy);
resume_copy.name = @"HanMeiMei";
resume_copy.gender = @"female";
resume_copy.universityInfo.major = @"TeleCommunication";
NSLog(@"\n\n\n======== original resume ======== %@\n\n\n======== revised copy resume ======== %@",resume,resume_copy);
上面的代码模拟了这样一个场景:李雷同学写了一份自己的简历,然后韩梅梅复制了一份并修改了姓名,性别和专业这三个和李雷不同的信息。
这里我们重写了Resume
的description
方法来看一下所有属性的值及其内存地址。最后来看一下resume对象和resume_copy对象打印的结果:
//================== Output log ==================
======== original resume ========
resume object address:0x604000247d10
name:LiLei | 0x10bc0c0b0
gender:male | 0x10bc0c0d0
age:24 | 0x10bc0c0f0
university name:X| 0x10bc0c110
university start year:2014 | 0x10bc0c130
university end year:2018 | 0x10bc0c150
university major:CS | 0x10bc0c170
======== copy resume ========
resume object address:0x604000247da0
name:LiLei | 0xa000069654c694c5
gender:male | 0xa000000656c616d4
age:24 | 0xa000000000034322
university name:X| 0xa000000000000581
university start year:2014 | 0xa000000343130324
university end year:2018 | 0xa000000383130324
university major:CS | 0xa000000000053432
======== original resume ========
resume object address:0x604000247d10
name:LiLei | 0x10bc0c0b0
gender:male | 0x10bc0c0d0
age:24 | 0x10bc0c0f0
university name:X| 0x10bc0c110
university start year:2014 | 0x10bc0c130
university end year:2018 | 0x10bc0c150
university major:CS | 0x10bc0c170
======== revised copy resume ========
resume object address:0x604000247da0
name:HanMeiMei | 0x10bc0c1b0
gender:female | 0x10bc0c1d0
age:24 | 0xa000000000034322
university name:X| 0xa000000000000581
university start year:2014 | 0xa000000343130324
university end year:2018 | 0xa000000383130324
university major:TeleCommunication | 0x10bc0c1f0
注:还可以用序列化和反序列化的办法来实现深复制,因为与代码设计上不是很复杂,很多语言直接提供了接口,故这里不做介绍。
下面我们看一下该例子对应的 UML类图,可以更直观地看一下各个成员之间的关系:
原型模式代码示例类图
在这里需要注意的是:
copy
方法是NSObject
类提供的复制本对象的接口。NSObject
类似于Java中的Object
类,在Objective-C中几乎所有的对象都继承与它。而且这个copy
方法也类似于Object
类的clone()
方法。copyWithZone(NSZone zone)
方法是接口NSCopying
提供的接口。而因为这个接口存在于实现文件而不是头文件,所以它不是对外公开的;即是说外部无法直接调用copyWithZone(NSZone zone)
方法。copyWithZone(NSZone zone)
方法是在上面所说的copy
方法调用后再调用的,作用是将对象的所有数据都进行复制。因此使用者需要在copyWithZone(NSZone zone)
方法里做工作,而不是copy
方法,这一点和Java的clone
方法不同。- (id)copyWithZone:(NSZone *)zone
方法; 或者协议,配合 copyWithZone:/mutableCopyWithZone:
方法Cloneable
接口并实现clone()
方法来复制该类的实例。