美文网首页
iOS 设计模式之十二(享元模式)

iOS 设计模式之十二(享元模式)

作者: 阿饼six | 来源:发表于2019-12-05 15:23 被阅读0次

    一、概念

    1、享元模式的动机

    ​ 现代社会都讲究资源节约,资源复用。在软件系统中,有时候也会存在资源浪费的情况,例如在计算机内存中存储了多个完全相同或者非常相似的对象,如果这些对象的数量太多将导致系统运行代价过高,而享元模式可以节约内存使用空间,实现对这些相同或者相似对象的共享访问。

    2、享元模式的定义

    享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

    3、享元对象的2个状态

    1)内部状态(Intrinsic State):内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。

    2)外部状态(Extrinsic State):外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。

    ​ 正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。

    4、享元模式的4个角色

    1)Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。

    2)ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。

    3)UnsharedConcreteFlyweight(非共享具体享元类):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。

    4)FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计。
    在享元模式中引入了享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。

    5、结构图
    享元模式

    二、示例

    ​ 享元类的设计是享元模式的要点之一,在享元类中要将内部状态和外部状态分开处理,通常将内部状态作为享元类的成员变量,而外部状态通过注入的方式添加到享元类中。享元工厂类是设计的核心。

    1)先创建一个Cell类,里面有内部状态和设置外部状态的方法,表示抽象享元类;

    2)再创建ImageCell和TextCell两个类,都继承自Cell类,表示具体享元类;

    3)最后创建一个CellFactory类,里面有一个享元池,表示享元工厂类。

    Cell类:

    @interface Cell : NSObject
    @property(nonatomic, copy, readonly) NSString *cellID; //内部状态
    - (void)setRowIndex:(NSInteger)rowIndex; //外部状态
    @end
    
    @implementation Cell
    - (NSString *)cellID {
        return @"cellID";
    }
    
    - (void)setRowIndex:(NSInteger)rowIndex {
        NSLog(@"Cell复用ID = %@, rowIndex = %ld", self.cellID, rowIndex);
    }
    @end
    

    ImageCell和TextCell类:

    // ImageCell
    @interface ImageCell : Cell
    
    @end
    @implementation ImageCell
    - (NSString *)cellID {
        return @"ImageCell";
    }
    @end
    
    // TextCell
    @interface TextCell : Cell
    
    @end
    @implementation TextCell
    - (NSString *)cellID {
        return @"TextCell";
    }
    @end
    

    CellFactory类:

    // .h文件
    @interface CellFactory : NSObject
    + (instancetype)sharedInstance;
    - (Cell *)getCellWithCellID:(NSString *)cellID;
    @end
    
    // .m文件
    @interface CellFactory ()
    @property(nonatomic, strong) NSMutableDictionary *dict; //享元池
    @end
    
    @implementation CellFactory
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            _dict = [NSMutableDictionary dictionary];
        }
        return self;
    }
    
    + (instancetype)sharedInstance { //单例模式
        static CellFactory *factory;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            factory = [CellFactory new];
        });
        return factory;
    }
    
    - (Cell *)getCellWithCellID:(NSString *)cellID { //享元工厂类核心代码
        if ([self.dict.allKeys containsObject:cellID]) {
            return [self.dict objectForKey:cellID];
        } else {
            if ([cellID isEqualToString:@"ImageCell"]) {
                ImageCell *imageCell = [ImageCell new];
                [self.dict setObject:imageCell forKey:cellID];
                return imageCell;
            } else if ([cellID isEqualToString:@"TextCell"]) {
                TextCell *textCell = [TextCell new];
                [self.dict setObject:textCell forKey:cellID];
                return textCell;
            } else {
                return nil;
            }
        }
    }
    @end
    

    运行代码:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        /**
         说明:UITableViewCell的复用机制与这里Demo不尽相同,需要查看UITableViewCell的复用机制请自行查阅资料
            这里的Demo就是展示享元模式设计的大概思路
         */
        CellFactory *factory = [CellFactory sharedInstance];
        Cell *imageCell1, *imageCell2, *imageCell3, *textCell1, *textCell2;
        imageCell1 = [factory getCellWithCellID:@"ImageCell"];
        imageCell2 = [factory getCellWithCellID:@"ImageCell"];
        imageCell3 = [factory getCellWithCellID:@"ImageCell"];
        NSLog(@"imageCell1 = %p", imageCell1);
        NSLog(@"imageCell2 = %p", imageCell2);
        NSLog(@"imageCell3 = %p", imageCell3);
        NSLog(@"-------------------");
        
        textCell1 = [factory getCellWithCellID:@"TextCell"];
        textCell2 = [factory getCellWithCellID:@"TextCell"];
        
        NSLog(@"textCell1 = %p", textCell1);
        NSLog(@"textCell2 = %p", textCell2);
        NSLog(@"-------------------");
        
        // 设置外部状态
        [imageCell1 setRowIndex:0];
        [imageCell2 setRowIndex:1];
        [textCell1 setRowIndex:2];
    }
    

    打印结果:从享元工厂类得到的对象内存地址一样,很好地复用了对象。

    imageCell1 = 0x600002dbcc70
    imageCell2 = 0x600002dbcc70
    imageCell3 = 0x600002dbcc70
    -------------------
    textCell1 = 0x600002dac660
    textCell2 = 0x600002dac660
    -------------------
    Cell复用ID = ImageCell, rowIndex = 0
    Cell复用ID = ImageCell, rowIndex = 1
    Cell复用ID = TextCell, rowIndex = 2
    

    三、总结

    ​ 当系统中存在大量相同或者相似的对象时,享元模式是一种较好的解决方案,它通过共享技术实现相同或相似的细粒度对象的复用,从而达到了“节约内存,提高性能”的效果。

    1、优点

    1、可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。

    2、享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

    2、缺点

    1、享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。

    2、为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。

    3、适用场景

    1、一个系统有大量相同或者相似的对象,造成内存的大量耗费。

    2、对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

    3、在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

    4、iOS应用举例

    ​ 例如在屏幕上显示500朵不同类型的花,假设这些花的类型只有5种,如果不使用享元模式那么就要创建500个UIImageView对象,耗费大量内存资源。如果使用享元模式,只需要创建5种UIImageView对象放进享元池,外部状态就是500朵花在屏幕中不同的位置,然后通过drawRect绘制最终的显示效果。

    Demo地址:iOS-Design-Patterns

    相关文章

      网友评论

          本文标题:iOS 设计模式之十二(享元模式)

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