原型模式(Prototype Pattern)用于创建重复的对象,同时保证性能,这种类型的设计模式属于创建型模式,这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆,当直接创建对象的代价比较大时,则采用这种模式.
例如:一个对象需要再一个高代价的数据库操作之后被创建,我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用
介绍
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
主要解决:在运行期建立和删除原型
何时使用:
1. 当一个系统应该独立于它的产品创建,构成和表示时
2. 当要实例化的类是在运行时刻指定时,例如,通过动态装载
3. 为了避免创建一个与产品类层次平行的工厂类层次时
4. 当一个类的实例只能有几个不同状态组合中的一种时,建立相应数目的原型克隆它们可能比每次用合适的状态手工实例化该类更方便一些
如何解决:利用已有的一个原型对象,快速的生成和原型对象一样的实例
如上图所示,原型模式是指,一个抽象类 Prototype 具有一个clone 方法,其实现类ConcretePrototype1、ConcretePrototype2 实现各自的clone方法,在使用的时候,调用Prototype的clone方法可以clone任意实现类。其作用就是快速创建一个新的对象。请看如下代码:
Prototype *type = [[ConcretePrototype1 alloc] init];
//(1)对type所持有的变量进行赋值。
type.a =
type.b =
//(2)保存type的现有状态
[array addObject:type];
//(3)继续变更type的信息。
type.a =
type.b =
在上诉代码中,在步骤(2)中我们需要暂存一下当前type的状态,以便后续做比较或者其他用途。按照上面的代码是无法实现我们的需求的,因为把type加到数组之后,之后type的值依然再改变。为了不让type的值变化,我们可能这样做
Prototype *type = [[ConcretePrototype1 alloc] init];
//(1)对type所持有的变量进行赋值。
type.a =
type.b =
//(2)保存type的现有状态
Prototype *tempType = [[ConcretePrototype1 alloc] init];
tempType.a = type.a
tempType.b = type.b
[array addObject:tempType];
//(3)继续变更type的信息。
type.a =
type.b =
想一想,你是否写过这样的代码?这样的代码有什么不好呢?
- 代码冗余,如果需要多次保存状态,可能需要写多个这样的赋值逻辑,当然,你可以把它抽出来作为一个单独的函数。
- 如果type对象的属性中包含了多个其他对象,那么简单的赋值操作并不能保存这些对象的状态,还需要去创建这些对象,并拷贝其内部属性,这是相当繁琐的工程。
看到这里你可能会想,我根本不会这么做,我会使用NSObject提供的copy方法,实现NSCopying 协议进行复制。你的想法非常赞,其实NSCopying就是Cocoa框架提供的一种原型模式,在讲解之前,我们先说一下,Objective-C的浅拷贝和深拷贝
深拷贝和浅拷贝
OC中的变量引用有值引用和指针引用。对于值引用而言,没有深拷贝和浅拷贝的区分,区别在于指针引用。我们先看浅拷贝和深拷贝模型
由上面两个模型可以看出深拷贝是将内存中的资源也进行了一份拷贝,而浅拷贝只是内存资源指针的拷贝
原型模式实践
填写表单是我们在生活中经常遇到,我们要填很多表单,比如说上学要填报名表,上班要填职位表等等,有时候我们填了一半忘记了关键信息或者有其他重要的事要先处理,我们需要先把现有表单信息保存下来,等有时间了再拿出来继续填写
首先我们需要一个表单协议
@protocol Form <NSObject, NSCopying>
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *address;
@property (strong, nonatomic) NSMutableArray *relatedForm;
- (void)printSelf;
- (void)addForm:(id)form;
- (id)copy;
@end
我们还有一个学校的表单:
@interface SchoolForm : NSObject <Form>
@end
@implementation SchoolForm
@synthesize address;
@synthesize name;
@synthesize relatedForm;
- (instancetype)init:(NSString *)name address:(NSString *)address
{
self = [super init];
if (self) {
self.name = name;
self.address = address;
self.relatedForm = [[NSMutableArray alloc] init];
}
return self;
}
- (void)printSelf {
NSLog(@"name=%@,address=%@", self.name, self.address);
}
- (void)addForm:(id)form {
[self.relatedForm addObject:form];
}
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
SchoolForm *form = [[self.class allocWithZone:zone] init:self.name address:self.address];
for (id<Form> tform in self.relatedForm) {
[form.relatedForm addObject:[tform copy]];
}
return form;
}
@end
使用:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
SchoolForm *schoolForm1 = [[SchoolForm alloc] init:@"清华大学" address:@"北京"];
SchoolForm *schoolForm2 = [[SchoolForm alloc] init:@"北京大学" address:@"北京"];
[schoolForm1 addForm:schoolForm2];
SchoolForm *copySchool = [schoolForm1 copy];
NSLog(@"copySchool");
}
@end
这里有一个非常有趣的问题,为什么要在Form协议里面添加一个copy方法,如果不写成copy,写为clone方法会怎么样?
- NSObject类里面含有copy方法,当调用copy方法的时候回自动调用copyWithZone方法,我们在SchoolForm的copyWithZone方法里遍历了所用相关的form
- 这些form的类型是在运行时才能确定的,可能是schoolForm也可能是JobForm等等。但无论是什么类型的Form,它都继承自NSObject。所以这是一个偷梁换柱的操作,看起来像是调用Form协议里的copy方法,其实是调用NSObject的copy方法。换成clone的话就不能使用NSCopying协议了
认识原型模式
1. 原型模式的功能
原型模式的功能实际上包含两个方面:
- 通过克隆来创建新的对象实例
- 克隆出来的新的对象实例赋值原型实例属性的值
原型模式要实现的主要功能:通过克隆来创建新的对象实例.一般来讲,新创建出来的实例的数据是和原型实例一样的.但具体如何实现克隆,需要有程序自行实现,原型模式并没有统一的要求和实现算法
2. 原型和new
原型模式从某种意义上说,就像是new操作,但请注意,只是"类似于new"而不是"就是new".
克隆方法与new操作最明显的不同在于:
new一个对象实例,一般属性是没有值的,或者是只有默认值.如果是克隆得到的一个实例,通常属性是有值的,属性的值就是原型对象实例在克隆的时候,原型对象实例的属性的值
3. 原型实例和克隆的实例
原型实例和克隆的实例,本质上是不同的实例,克隆完成后,它们之间是没有关联的,如果克隆完成后,克隆出来的实例的属性的值发生了改变,是不会影响到原型实例的,也就是说它们指向不同的内存空间
4. 原型模式的本质
原型模式的本质:克隆生成对象。
克隆是手段,目的还是生成新的对象实例.正因为原型的目的是为了生成新的对象实例,原型模式通常被归类为创建型的模式
原型模式的重心还是在创建新的对象实例,至于创建出来的对象,其属性的值是否一定要和原型对象属性的值完全一样,这个并没有强制规定,只不过在目前大多数实现中,克隆出来的对象和原型对象的属性值是一样的。也就是说,可以通过克隆来创造值不一样的实例,但是对象类型必须一样,可以有部分甚至是全部的属性的值不一样,可以有选择性的克隆,比如上面的代码:
SchoolForm *form = [[self.class allocWithZone:zone] init:self.name address:self.address];
克隆我可以选择一部分属性赋值,而不是全部属性属性的值,看需求而定