一、概念
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
网友评论