设计模式:原型模式

概述

我们知道设计模式方面的知识是一个初中级工程师进阶高级工程师过程中一道无法跨越的屏障,学好它并将它应用到自己的项目中是一件充满乐趣和成就感的事情。本文将讲述设计模式中的原型模式,通过阅读本文你将收获如下内容:

  • 什么是原型模式以及它的作用。
  • 什么时间使用原型模式。
  • Objective-C中深拷贝和浅拷贝
  • 原型模式的具体实践。

下面我们分条讲述。

原型模式及其作用

原型模式

如上图所示,原型模式是指,一个抽象类 Prototype 具有一个clone 方法,其实现类ConcretePrototype1ConcretePrototype2 实现各自的clone方法,在使用的时候,调用Prototype的clone方法可以clone任意实现类。其作用就是快速创建一个新的对象。请看如下代码:

1
2
3
4
5
6
7
8
9
10
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的值变化,我们可能这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 =

想一想,你是否写过这样的代码?这样的代码有什么不好呢?

  1. 代码冗余,如果需要多次保存状态,可能需要写多个这样的赋值逻辑,当然,你可以把它抽出来作为一个单独的函数。
  2. 如果type对象的属性中包含了多个其他对象,那么简单的赋值操作并不能保存这些对象的状态,还需要去创建这些对象,并拷贝其内部属性,这是相当繁琐的工程。

看到这里你可能会想,我根本不会这么做,我会使用NSObject提供的copy方法,实现NSCopying 协议进行复制。你的想法非常赞,其实NSCopying就是Cocoa框架提供的一种原型模式,在讲解之前,我们先说一下,Objective-C的浅拷贝和深拷贝。

何时使用原型模式

  • 需要创建的对象应独立于其类型与创建方式。
  • 要实例化的类是在运行时决定的。
  • 不想要与产品层次相对应的工厂层次。
  • 不同类的实例间的差异仅是状态的若干组合。因此复制相应数量的原型比手工实例化更加方便。
  • 类不容易创建,比如每个组件可把其他组件作为子节点的组合对象。复制已有的组合对象并对副本进行修改会更加容易。
  • 从功能的角度来讲,不管什么对象,只要复制自身比手工实例化要好,都可以是原型对象。

在以下两种特别常见的情形,我们会考虑使用此模式:

  • 有很多相关的类,其行为略有不同,而且主要差异在于内部属性,如名称,图像等。
  • 需要使用组合(树型)对象作为其他东西的基础。例如,使用组合对象作为组件来构建另一个组合对象。

浅拷贝和深拷贝

我们知道,OC中的变量引用有值引用和指针引用。对于值引用而言,没有深拷贝和浅拷贝的区分,区别在于指针引用。我们先看浅拷贝模型。

浅拷贝

深拷贝

深拷贝

由上面两个模型可以看出深拷贝是将内存中的资源也进行了一份拷贝,而浅拷贝只是内存资源指针的拷贝。

原型模式的实践

填写表单使我们在我们生活中经常遇到,我们要填很多表单,比如说上学要填报名表,上班要填职位表等等,有时候我们填了一般忘记了关键信息或者有其他重要的事要先处理,我们需要先把现有表单信息保存下来,等有时间了再拿出来继续填写。

首先我们需要一个表单协议

1
2
3
4
5
6
7
8
9
10
@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

我们还有一个学校的表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@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

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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协议了。

参考

本文参考《Objective-C编程之道:iOS设计模式解析》
你可以在公众号里回复”资源”得到本书的电子版,当然建议购买正版。

其他

本文首发于RiverLi的个人公众号,如需转载请与我本人联系。
由于本博客暂未开放评论功能,如有任何问题,请关注我的公众号(RiverLi),给我留言,或者在对应的文章评论。
RiverLi的公众号