美文网首页
iOS设计模式之原型模式

iOS设计模式之原型模式

作者: 点滴86 | 来源:发表于2024-05-02 23:02 被阅读0次

原型模式

原型模式的原理与应用

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段相同),在这种情况下,可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式。

那何为“对象的创建成本比较大”?

实际上,创建对象包含的申请内存、给成员变量赋值这一过程,本身并不会花费太多时间,或者说对于大部分业务系统来说,这点时间完全是可以忽略的。应用一个复杂的模式,只得到一点点的性能提升,这就是所谓的过度设计,得不偿失。
但是,如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从RPC、网络、数据库、文件系统等非常慢速的IO中读取,这种情况下,就可以利用原型模式,从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候,都重复执行这些耗时的操作。
假设数据库中存储了大约10万条“搜索关键词”信息,每条信息包含关键词、关键词被搜素的次数、信息最近被更新的时间等。系统A在启动的时候会加载这份数据到内存中,用于处理某些其他的业务需求。为了方便快速地查找某个关键词对应的信息,我们给关键词简历一个散列表索引。字典的key为搜索关键词,value为关键词详细信息(比如搜索次数)。只需要将数据从数据库中读取出来,放入字典就可以了。
另外一个系统B,专门用来分析搜索日志,定期(比如间隔10分钟)批量地更新数据库中的数据,并且标记为新的数据版本。比如,在下面的示例图中,对V2版本的数据进行更新,得到V3版本的数据。这里假设只有更新和新添加关键词,没有删除关键词的行为。

prototype01.png

为了保证系统A中数据的实时性(不一定非常实时,但数据也不能太旧),系统A需要定期根据数据库中的数据,更新内存中的索引和数据。
该如何实现这个需求呢?
只需要在系统A中,记录当前数据的版本Va对应的更新时间Ta,从数据库中捞出更新时间大于Ta的所有关键词,也就是Va版本与最新版本数据的“差集”,然后针对差集中的每个关键词进行处理。如果它已经在散列表中存在了,就更新相应的搜素次数、更新时间等信息;如果它在散列表中不存在,就将它插入到散列表中。代码如下:

// 遵守 NSCopying 协议
@interface DMSearchWord : NSObject <NSCopying>

@property (nonatomic, strong) NSString *keyWord;

@property (nonatomic, assign) NSInteger count;

@property (nonatomic, assign) NSInteger lastUpdateTime;

@end

- (void)version1
{
    // 当前数据库的版本VA和更新时间
    NSMutableDictionary *currentKeywords = [[NSMutableDictionary alloc] init];
    NSInteger lastUpdateTime = 0;
    
    // 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
    NSArray<DMSearchWord *> *searchWords = [self getSearchWords:lastUpdateTime];
    
    for (DMSearchWord *searchWord in searchWords) {
        if (searchWord.lastUpdateTime > lastUpdateTime) {
            lastUpdateTime = searchWord.lastUpdateTime;
        }
        [currentKeywords setObject:searchWord forKey:searchWord.keyWord];
    }
}

// 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
- (NSArray<DMSearchWord *> *)getSearchWords:(NSInteger)lastUpdateTime
{
    NSArray<DMSearchWord *> *array = [[NSArray<DMSearchWord *> alloc] init];
    
    return array;
}

不过,现在有一个特殊的要求:任何时可,系统A中的所有数据都必须是同一个版本的,要么都是版本a,要么都是版本b,不能有的是版本a,有的是版本b。那刚刚的更新方式就不能满足这个要求了。除此之外,还要求:在更新内存数据的时候,系统A不能处于不可用状态,也就是不能停机更新数据。
该如何实现现在这个需求呢?
把正在使用的数据的版本定义为“服务版本”,当要更新内存中的数据的时候,并不是直接在服务版本(假设是版本a数据)上更新,而是重新创建另一个版本数据(假设是版本b数据),等新的版本数据建好之后,再一次性地将服务版本从版本a切换到版本b。这样既保证了数据一直可用,又避免了中间状态的存在。代码实现如下:

- (void)version2
{
    // 当前数据库的版本VA
    NSMutableDictionary *currentKeywords = [[NSMutableDictionary alloc] init];
    
    // 从数据库中取出所有的数据, 这个过程比较耗时
    NSArray<DMSearchWord *> *searchWords = [self getAllSearchWords];
    NSMutableDictionary *newKeywords = [[NSMutableDictionary alloc] init];
    for (DMSearchWord *searchWord in searchWords) {
        [newKeywords setObject:searchWord forKey:searchWord.keyWord];
    }
    
    // 新的版本数据建好之后,再一次性地将服务版本从版本 a 切换到版本 b
    currentKeywords = newKeywords;
}

- (NSArray<DMSearchWord *> *)getAllSearchWords
{
    // 从数据库中取出所有的数据
    NSArray<DMSearchWord *> *array = [[NSArray<DMSearchWord *> alloc] init];
    
    return array;
}

不过,在上面的代码实现中,newKeywords构建的成本比较高。需要将这10万条数据从数据库中读出,然后计算哈希值,构建newKeywords。这个过程显然是比较耗时。为了提高效率,原型模式就派上用场了。
拷贝currentKeywords数据到newKeywords中。然后从数据库中只捞出新增或者有更新的关键词,更新到newKeywords中。相对于10万条数据来说,每次新增或者更新的关键词个数是比较少的,所以,这种策略大大提高了数据更新的效率。代码实现如下:

- (void)version3
{
    // 当前数据库的版本VA和更新时间
    NSMutableDictionary *currentKeywords = [[NSMutableDictionary alloc] init];
    NSInteger lastUpdateTime = 0;
    
    // 新的版本数据 构建自 当前数据库版本的复制
    NSMutableDictionary *newKeywords = [currentKeywords mutableCopy];
    
    // 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
    NSArray<DMSearchWord *> *searchWords = [self getSearchWords:lastUpdateTime];
    
    for (DMSearchWord *searchWord in searchWords) {
        if (searchWord.lastUpdateTime > lastUpdateTime) {
            lastUpdateTime = searchWord.lastUpdateTime;
        }
        if ([[newKeywords allKeys] containsObject:searchWord.keyWord]) {
            DMSearchWord *oldSearchWord = [newKeywords objectForKey:searchWord.keyWord];
            oldSearchWord.count = searchWord.count;
            oldSearchWord.lastUpdateTime = searchWord.lastUpdateTime;
        } else {
            [newKeywords setObject:searchWord forKey:searchWord.keyWord];
        }
    }
    
    // 新的版本数据建好之后,再一次性地将服务版本从版本 a 切换到版本 b
    currentKeywords = newKeywords;
}

// 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
- (NSArray<DMSearchWord *> *)getSearchWords:(NSInteger)lastUpdateTime
{
    NSArray<DMSearchWord *> *array = [[NSArray<DMSearchWord *> alloc] init];
    
    return array;
}

在上面的代码中,通过mutableCopy浅拷贝方法来实习那原型模式。通过newKeywords更新DMSearchWord对象的时候(比如,更新“设计模式”这个搜索关键词的访问次数),newKeywords和currentKeywords因为指向相同的一组DMSearchWord对象,就会导致currentKeywords中指向的DMSearchWord,有的是老版本的,有的是新版本的,就没法满足之前的需求:currentKeywords中的数据在任何时候都是同一个版本的,不存在介于老版本与新版本之间的中间状态。
该如何解决这个问题呢?
可以将浅拷贝替换为深拷贝。newKeywords不仅仅复制currentKeywords的索引,还把DMSearchWord对象也复制一份出来。这样newKeywords和currentKeywords就指向不同的DMSearchWord对象,也就不存在更新newKeywords的数据会导致currentKeywords的数据也被更新的问题了。
如何实现深拷贝呢?递归拷贝对象、对象的引用对象以及引用对象的引用对象......知道要拷贝的对象只包含基本数据类型数据,没有引用对象为止。根据这个思路对代码进行重构,重构之后的代码如下:

// 遵守 NSCopying 协议
@interface DMSearchWord : NSObject <NSCopying>

@property (nonatomic, strong) NSString *keyWord;

@property (nonatomic, assign) NSInteger count;

@property (nonatomic, assign) NSInteger lastUpdateTime;

@end

@implementation DMSearchWord

// 实现 copyWithZone: 方法
- (id)copyWithZone:(NSZone *)zone
{
    DMSearchWord *searchWord = [[DMSearchWord allocWithZone:zone] init];
    searchWord.keyWord = self.keyWord;
    searchWord.count = self.count;
    searchWord.lastUpdateTime = self.lastUpdateTime;
    
    return searchWord;
}

@end

- (void)version4
{
    // 当前数据库的版本VA和更新时间
    NSMutableDictionary *currentKeywords = [[NSMutableDictionary alloc] init];
    NSInteger lastUpdateTime = 0;
    
    // 新的版本数据 构建自 当前数据库版本的复制
    NSMutableDictionary *newKeywords = [[NSMutableDictionary alloc] init];
    for (DMSearchWord *searchWord in currentKeywords.allValues) {
        DMSearchWord *newSearchWord = [searchWord copy];
        [newKeywords setObject:newSearchWord forKey:newSearchWord.keyWord];
    }
    
    // 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
    NSArray<DMSearchWord *> *searchWords = [self getSearchWords:lastUpdateTime];
    
    for (DMSearchWord *searchWord in searchWords) {
        if (searchWord.lastUpdateTime > lastUpdateTime) {
            lastUpdateTime = searchWord.lastUpdateTime;
        }
        if ([[newKeywords allKeys] containsObject:searchWord.keyWord]) {
            DMSearchWord *oldSearchWord = [newKeywords objectForKey:searchWord.keyWord];
            oldSearchWord.count = searchWord.count;
            oldSearchWord.lastUpdateTime = searchWord.lastUpdateTime;
        } else {
            [newKeywords setObject:searchWord forKey:searchWord.keyWord];
        }
    }
    
    // 新的版本数据建好之后,再一次性地将服务版本从版本 a 切换到版本 b
    currentKeywords = newKeywords;
}

// 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
- (NSArray<DMSearchWord *> *)getSearchWords:(NSInteger)lastUpdateTime
{
    NSArray<DMSearchWord *> *array = [[NSArray<DMSearchWord *> alloc] init];
    
    return array;
}

深拷贝都要比浅拷贝耗时、耗内存空间。针对这个应用场景,有没有更快、更省内存的实现方式呢?
可以先采用浅拷贝的方式创建newKeywords。对于需要更新的DMSearchWord对象,再使用深拷贝的方式创建一份新的对象,替换newKeywords中的老对象。毕竟需要更新的数据是很少的。这种方式即利用了浅拷贝节省时间、空间的优点,又能保证currentKeywords中的数据都是老版本的数据。代码实现如下:

- (void)version5
{
    // 当前数据库的版本VA和更新时间
    NSMutableDictionary *currentKeywords = [[NSMutableDictionary alloc] init];
    NSInteger lastUpdateTime = 0;
    
    // 新的版本数据 构建自 当前数据库版本的复制
    NSMutableDictionary *newKeywords = [currentKeywords mutableCopy];
    
    // 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
    NSArray<DMSearchWord *> *searchWords = [self getSearchWords:lastUpdateTime];
    
    for (DMSearchWord *searchWord in searchWords) {
        if (searchWord.lastUpdateTime > lastUpdateTime) {
            lastUpdateTime = searchWord.lastUpdateTime;
        }
        [newKeywords setObject:searchWord forKey:searchWord.keyWord];
    }
    
    // 新的版本数据建好之后,再一次性地将服务版本从版本 a 切换到版本 b
    currentKeywords = newKeywords;
}

// 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中
- (NSArray<DMSearchWord *> *)getSearchWords:(NSInteger)lastUpdateTime
{
    NSArray<DMSearchWord *> *array = [[NSArray<DMSearchWord *> alloc] init];
    
    return array;
}

相关文章

网友评论

      本文标题:iOS设计模式之原型模式

      本文链接:https://www.haomeiwen.com/subject/taaqfjtx.html