iOS 设计模式之五(原型模式)

2019-12-05  本文已影响0人  阿饼six

一、概念

1、原型模式的动机

​ 有时候我们需要克隆多份一样的东西,比如在电脑中:复制和粘贴,快捷键通常为Ctrl + C和Ctrl + V,通过对已有对象的复制和粘贴,我们可以创建大量的相同对象。如何在一个面向对象系统中实现对象的复制和粘贴呢,原型模式正为解决此类问题而诞生。

2、原型模式的定义

原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。

在使用原型模式时,我们需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。

3、浅克隆和深克隆

浅克隆:

​ 浅克隆(ShallowClone):也叫浅拷贝,在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

深克隆:

​ 深克隆(DeepClone):也叫深拷贝,在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

如果对iOS 浅克隆和深克隆感兴趣,可以查看-iOS 内存管理

4、原型模式的三个角色

1)Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。

2)ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。

3)Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。

​ 原型模式的核心在于如何实现克隆方法

5、结构图
原型模式

二、示例

1)首先创建一个协议CloneProtocol,协议里有copy方法,还有一些业务方法,表示抽象原型类;

2)然后创建一个Person对象,一个Car对象,都遵循于CloneProtocol协议,分别在Person和Car的.m文件实现协议的方法,表示具体原型类。

3)因为测试代码就写在ViewDidLoad中,所以暂且认为ViewController是客户类。

具体代码如下:

CloneProtocol协议:

// 协议声明出来的是方法,虽然看起来是属性,其实只有 getter、setter 这两个方法,内部是没有实例变量的
@protocol CloneProtocol <NSObject>
@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign, readonly) NSInteger carsCount;
-(id)copy;
-(id)initWithName:(NSString *)name;
-(void)addCar:(Car *)car;
-(void)removeCar:(Car *)car;
@end

Person类:

// .h文件
@interface Person : NSObject<CloneProtocol>
@end

// .m文件
@implementation Person
/**
@synthesize 三个作用:
1、生成带下划线变量
2、生成setter方法
3、生成getter方法
*/
@synthesize name = _name;

- (instancetype)initWithName:(NSString *)name
{
    self = [super init];
    if (self) {
        _name = name;
        _carsArr = [NSMutableArray array];
    }
    return self;
}

- (void)addCar:(Car *)car {
    [self.carsArr addObject:car];
}

- (void)removeCar:(Car *)car {
    if ([self.carsArr containsObject:car]) {
        [self.carsArr removeObject:car];
    }
}

- (NSInteger)carsCount {
    return self.carsArr.count;
}

- (NSInteger)getCarsCount {
    return self.carsArr.count;
}

- (id)copyWithZone:(NSZone *)zone{
    Person *p = [[[self class] alloc] initWithName:_name]; //创建新对象
    for (Car *car in self.carsArr) {
        Car *c = [car copy]; //把Person的Car也复制一份
        [p addCar:c];
    }
    return p;
}
@end

Car类:

// .h文件
@interface Car : NSObject<CloneProtocol>
@end

// .m文件
@implementation Car
@synthesize name = _name;

- (instancetype)initWithName:(NSString *)name
{
    self = [super init];
    if (self) {
        _name = name;
    }
    return self;
}

- (id)copyWithZone:(NSZone *)zone {
    Car *car = [[[self class] alloc] initWithName:_name]; //创建一份新的对象
    return car;
}

- (void)addCar:(Car *)car {
    
}

- (void)removeCar:(Car *)car {
    
}

- (NSInteger)carsCount {
    return 0;
}
@end

运行代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 测试数组Array的copy方法
    NSArray *arr1 = [NSArray array];
    NSArray *arr2 = [arr1 copy];
    NSLog(@"arr1 = %p", arr1);
    NSLog(@"arr2 = %p", arr2);
    NSLog(@"----------------");
    
    Car *car1 = [[Car alloc] initWithName:@"兰博基尼"];
    Car *car2 = [car1 copy];
    Car *car3 = [car1 copy];
    car3.name = @"法拉利";
    
    NSLog(@"Car1 = %p,name = %@", car1, car1.name);
    NSLog(@"Car2 = %p,name = %@", car2, car2.name);
    NSLog(@"Car3 = %p,name = %@", car3, car3.name);
    NSLog(@"----------------");
    
    Person *person1 = [[Person alloc] initWithName:@"小明"];
    [person1 addCar:car1];
    [person1 addCar:car2];
    [person1 addCar:car3];
    
    Person *person2 = [person1 copy];
    NSLog(@"person1 = %p, count = %ld", person1, person1.carsCount);
    NSLog(@"person2 = %p, count = %ld", person2, person2.carsCount);
    NSLog(@"----------------");
    
    [person1 removeCar:car3];
    NSLog(@"person1 = %p, count = %ld", person1, person1.carsCount);
    NSLog(@"person2 = %p, count = %ld", person2, person2.carsCount);
}

打印结果:

// Array调用copy,地址一样,只是拷贝了指针,还是同一个对象
arr1 = 0x7fff805efd90
arr2 = 0x7fff805efd90
----------------
// Car成功拷贝,地址都不同
Car1 = 0x6000018bc350,name = 兰博基尼
Car2 = 0x6000018bc300,name = 兰博基尼
Car3 = 0x6000018bc340,name = 法拉利
----------------
// Person成功拷贝,地址不同
person1 = 0x600001aec000, count = 3
person2 = 0x600001aec040, count = 3
----------------
// 操作person1并不会影响到person2
person1 = 0x600001aec000, count = 2
person2 = 0x600001aec040, count = 3

三、总结

​ 原型模式作为一种快速创建大量相同或相似对象的方式,在软件开发中应用较为广泛,很多软件提供的复制(Ctrl + C)和粘贴(Ctrl + V)操作就是原型模式的典型应用。

1、优点

1、当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。

2、扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。

3、原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。

4、可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。

2、缺点

1、需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。

2、在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

3、适用场景

1、创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。

2、如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。

3、需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

4、iOS应用举例

​ 在Objective-C中使用原型模式, 首先要遵循NSCoping协议(OC中一些内置类遵循该协议, 例如NSArray, NSMutableArray等)。

Demo地址:iOS-Design-Patterns

上一篇下一篇

猜你喜欢

热点阅读