定义

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

2个状态

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

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

模式结构

享元模式包含如下角色:

  • Flyweight: 抽象享元类:通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • ConcreteFlyweight: 具体享元类:它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。
  • UnsharedConcreteFlyweight: 非共享具体享元类:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • FlyweightFactory: 享元工厂类:享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。 image.png FlyweightFactory
    Flyweight
    -flyweights:HashMap
    toperation(extrinsicState)
    +getFlyweight(Stingey:weight
    it(ilyweights.containskey(key)
    UnsharedConcreteFlyweight
    ConcreteFlyweight
    return(Flyweight)flyweights.get(key
    else
    -allstate
    -intrinsicState
    FlyweightwnewConcreteFlyweighto;
    flyweights.put(key.w:
    +operation(extrinsicstate)
    +operation(extrinsicstate)
    returnfw.

使用场景

享元模式主要用于减少同一类对象的大量创建,以减少内存占用,提高项目流畅度,在iOS开发中,大家肯定都用过UITableViewCell,UICollectionViewCell,这两个类在使用过程中就使用了享元模式,工作原理基本就是:利用重用池重用思想,创建页面可显示的cell个数的对象,在页面滚动过程中监听每个cell的状态,从页面消失的cell被放回重用池,将要显示的cell先去重用池中去取,如果可以取到,则继续使用这个cell,如果没有多余的cell,就重新创建新的,这样即使你有100条数据,也仅仅只会创建页面可显示个数的cell对象,这样就大大减少了对象的创建,实现了大量内存占用,导致内存泄露的问题。

代码分析

享元类的设计是享元模式的要点之一,在享元类中要将内部状态和外部状态分开处理,通常将内部状态作为享元类的成员变量,而外部状态通过注入的方式添加到享元类中。享元工厂类是设计的核心。
1)先创建一个Cell类,里面有内部状态和设置外部状态的方法,表示抽象享元类;
2)再创建ImageCell和TextCell两个类,都继承自Cell类,表示具体享元类;
3)最后创建一个CellFactory类,里面有一个享元池,表示享元工厂类。
Cell类:

  1. @interface Cell : NSObject
  2. @property(nonatomic, copy, readonly) NSString *cellID; //内部状态
  3. - (void)setRowIndex:(NSInteger)rowIndex; //外部状态
  4. @end
  5. @implementation Cell
  6. - (NSString *)cellID {
  7. return @"cellID";
  8. }
  9. - (void)setRowIndex:(NSInteger)rowIndex {
  10. NSLog(@"Cell复用ID = %@, rowIndex = %ld", self.cellID, rowIndex);
  11. }
  12. @end

ImageCell和TextCell类:

  1. // ImageCell
  2. @interface ImageCell : Cell
  3. @end
  4. @implementation ImageCell
  5. - (NSString *)cellID {
  6. return @"ImageCell";
  7. }
  8. @end
  9. // TextCell
  10. @interface TextCell : Cell
  11. @end
  12. @implementation TextCell
  13. - (NSString *)cellID {
  14. return @"TextCell";
  15. }
  16. @end

CellFactory类:

  1. // .h文件
  2. @interface CellFactory : NSObject
  3. + (instancetype)sharedInstance;
  4. - (Cell *)getCellWithCellID:(NSString *)cellID;
  5. @end
  6. // .m文件
  7. @interface CellFactory ()
  8. @property(nonatomic, strong) NSMutableDictionary *dict; //享元池
  9. @end
  10. @implementation CellFactory
  11. - (instancetype)init
  12. {
  13. self = [super init];
  14. if (self) {
  15. _dict = [NSMutableDictionary dictionary];
  16. }
  17. return self;
  18. }
  19. + (instancetype)sharedInstance { //单例模式
  20. static CellFactory *factory;
  21. static dispatch_once_t onceToken;
  22. dispatch_once(&onceToken, ^{
  23. factory = [CellFactory new];
  24. });
  25. return factory;
  26. }
  27. - (Cell *)getCellWithCellID:(NSString *)cellID { //享元工厂类核心代码
  28. if ([self.dict.allKeys containsObject:cellID]) {
  29. return [self.dict objectForKey:cellID];
  30. } else {
  31. if ([cellID isEqualToString:@"ImageCell"]) {
  32. ImageCell *imageCell = [ImageCell new];
  33. [self.dict setObject:imageCell forKey:cellID];
  34. return imageCell;
  35. } else if ([cellID isEqualToString:@"TextCell"]) {
  36. TextCell *textCell = [TextCell new];
  37. [self.dict setObject:textCell forKey:cellID];
  38. return textCell;
  39. } else {
  40. return nil;
  41. }
  42. }
  43. }
  44. @end

运行代码:

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. /**
  4. 说明:UITableViewCell的复用机制与这里Demo不尽相同,需要查看UITableViewCell的复用机制请自行查阅资料
  5. 这里的Demo就是展示享元模式设计的大概思路
  6. */
  7. CellFactory *factory = [CellFactory sharedInstance];
  8. Cell *imageCell1, *imageCell2, *imageCell3, *textCell1, *textCell2;
  9. imageCell1 = [factory getCellWithCellID:@"ImageCell"];
  10. imageCell2 = [factory getCellWithCellID:@"ImageCell"];
  11. imageCell3 = [factory getCellWithCellID:@"ImageCell"];
  12. NSLog(@"imageCell1 = %p", imageCell1);
  13. NSLog(@"imageCell2 = %p", imageCell2);
  14. NSLog(@"imageCell3 = %p", imageCell3);
  15. NSLog(@"-------------------");
  16. textCell1 = [factory getCellWithCellID:@"TextCell"];
  17. textCell2 = [factory getCellWithCellID:@"TextCell"];
  18. NSLog(@"textCell1 = %p", textCell1);
  19. NSLog(@"textCell2 = %p", textCell2);
  20. NSLog(@"-------------------");
  21. // 设置外部状态
  22. [imageCell1 setRowIndex:0];
  23. [imageCell2 setRowIndex:1];
  24. [textCell1 setRowIndex:2];
  25. }

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

  1. imageCell1 = 0x600002dbcc70
  2. imageCell2 = 0x600002dbcc70
  3. imageCell3 = 0x600002dbcc70
  4. -------------------
  5. textCell1 = 0x600002dac660
  6. textCell2 = 0x600002dac660
  7. -------------------
  8. Cell复用ID = ImageCell, rowIndex = 0
  9. Cell复用ID = ImageCell, rowIndex = 1
  10. Cell复用ID = TextCell, rowIndex = 2

优点

  • 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
  • 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。

    缺点

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

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

    总结

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