定义
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
2个状态
- 内部状态(Intrinsic State):内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享。
- 外部状态(Extrinsic State):外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部。
正因为区分了内部状态和外部状态,我们可以将具有相同内部状态的对象存储在享元池中,享元池中的对象是可以实现共享的,需要的时候就将对象从享元池中取出,实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。
模式结构
享元模式包含如下角色:
- Flyweight: 抽象享元类:通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
- ConcreteFlyweight: 具体享元类:它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。
- UnsharedConcreteFlyweight: 非共享具体享元类:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
- FlyweightFactory: 享元工厂类:享元工厂类的作用在于提供一个用于存储享元对象的享元池,当用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
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类:
@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