设计模式之(四)原型模式

本文首发于个人博客

什么是原型模式

原型模式定义

参考wikipedia中的定义原型模式

原型模式是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。

原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据。

简单来说就是:可以通过深拷贝来快速而方便的创建一个新对象

深拷贝和浅拷贝

百度百科中对深拷贝和浅拷贝这么说明的

  • 浅拷贝

    • 拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间,浅拷贝只是一种简单的拷贝,让几个对象公用一个内存,然而当内存销毁的时候,指向这个内存空间的所有指针需要重新定义,不然会造成野指针错误。
  • 深拷贝

    • 一个引用对象一般来说由两个部分组成:一个具名的Handle,也就是我们所说的声明(如变量)和一个内部(不具名)的对象,也就是具名Handle的内部对象。它在Manged Heap(托管堆)中分配,一般由新增引用对象的New方法是进行创建。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。

原型模式结构

对应结构如下

Prototype声明了复制自身的接口。作为Prototype的实现,ConcretePrototype实现了复制自身的clone操作。这里的客户端是指使用了原型类实例的类。客户端通过clone创建了一个新的对象,即prototype的副本

什么时候使用原型模式?

  • 需要创建的对象不依赖于具体的类型以及创建方式
  • 具体实例化的对象类型是在运行期决定的
  • 不同类型之间的差异紧紧是状态的组合
  • 类型创建复杂,例如类型有复杂的嵌套

实例

有类YZPerson继承自NSObject,有两个属性name 和 age. 因为NSObject NSCopying

1
2
3
4
5
6
#import <UIKit/UIKit.h>

@interface YZPerson : NSObject
@property (nonatomic,strong) NSString *name;
@property (nonatomic ,assign) int age;
@end

使用Cocoa Touch框架中的对象复制

Cocoa Touch框架为NSObject的派生类提供了实现深拷贝的协议。NSObject的子类需要实现NSCopying协议以及其方法:

1
-(id)copyWithZone:(NSZone *)zone;

NSObject有一个实列方法叫做-(id)copy。默认的copy方法调用[self copyWithZone:nil];对于采纳了NSCopying协议的子类,需要实现这个方法,否则会引发异常。

不实现 copyWithZone:

ViewController.m中,有如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)viewDidLoad {
[super viewDidLoad];

YZPerson *p1 = [[YZPerson alloc] init];
p1.name = @"jack";
p1.age = 10;


YZPerson * p2 = [p1 copy];
p2.name = @"rose";
NSLog(@"p1 = %@,p1.name = %@, p1.age = %d",p1,p1.name,p1.age);
NSLog(@"p2 = %@,p2.name = %@, p2.age = %d",p2,p2.name,p2.age);
}

运行起来时候,会崩溃

1
2
Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: '-[YZPerson copyWithZone:]: unrecognized selector sent to instance 0x2832857e0'

由崩溃可知,是找不到copyWithZone的实现。所以,YZPerson.m中,写如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
#import "YZPerson.h"

@implementation YZPerson

- (id)copyWithZone:(nullable NSZone *)zone{

YZPerson *p = [[YZPerson allocWithZone:zone] init];
p.name = self.name;
p.age = self.age;
return p;
}

@end

ViewController.m中,有如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)viewDidLoad {
[super viewDidLoad];

YZPerson *p1 = [[YZPerson alloc] init];
p1.name = @"jack";
p1.age = 10;


YZPerson * p2 = [p1 copy];
p2.name = @"rose";
NSLog(@"p1 = %@,p1.name = %@, p1.age = %d",p1,p1.name,p1.age);
NSLog(@"p2 = %@,p2.name = %@, p2.age = %d",p2,p2.name,p2.age);
}

输出为

1
2
iOS-原型模式[5950:1131770] p1 = <YZPerson: 0x2826c2e00>,p1.name = jack, p1.age = 10
iOS-原型模式[5950:1131770] p2 = <YZPerson: 0x2826c2de0>,p2.name = rose, p2.age = 10

小结

使用- (id)copyWithZone:(nullable NSZone *)zone方法实现了深拷贝,通过copy方法(该方法默认调用copyWithZone方法)复制得到p2,从结果可以看出:深复制对象和和源对象的地址是不一样的:

需要注意的是,- (id)copyWithZone:(NSZone *)zone;已经被废弃,本案例仅仅为了说明这个设计模式而已。

zone
This parameter is ignored. Memory zones are no longer used by Objective-C.

1
2
p1 = <YZPerson: 0x2826c2e00>
p2 = <YZPerson: 0x2826c2de0>

Demo地址