iOS 设计模式的应用 ⑲ 享元模式

前言

    在生活中,公共设有多个车站,乘客沿着路线在接近他们目的地的车站上下车。到达目的地的费用仅与行程有关,跟私有车辆相比,乘坐公共交通要便宜的多。同时,大量去往相同方向的乘客可以分担维修和经营车辆(公共汽车、地铁)的费用。这就是利用公共资源的好处。

    在面向对象软件设计中,利用公共对象可以节省资源和提高性能。比方说,某个任务需要一个类的一百万个实例,但我们可以把这个类的一个实例让大家共享,而把某些独特的信息放在外部,节省的资源相当可观。共享的对象只提供某些内在的信息,而不能用来识别对象。专门用于设计可共享对象的一种设计模式叫做享元模式。

什么是享元模式

    所谓”享元“,就是被共享的单元。实现享元模式需要两个关键组件,通常是可共享的享元对象和保存它们的池。某种中央对象(工厂是这一角色的理想候选)维护这个池,并从它返回适当的实例。享元模式的意图是复用对象,节省内存。其主要用于减少创建对象的数量,以减少内存占用和提高性能。其尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

享元数据的类图.png

    Flyweight 是两个具体享元类 ConcreteFlyweight1ConcreteFlyweight2 的父接口(协议)。每个 ConcreteFlyweight 类维护不能用于识别对象的内在状态 intrinsicStateFlyweight 声明了 operation:extrinsicState 方法,由 ConcreteFlyweight 类实现。 intriinsicState 是享元部分可被共享的部分,而 extrinsicState 补充缺少的信息,让享元对象唯一。

什么时候使用享元模式

  • 系统有大量相似对象
  • 这些对象消耗大量内存。
  • 对象的外在状态可以放到外部而轻量化
  • 移除了外在状态后,可以用较少的共享对象替代原来的那组对象
  • 应用程序不依赖于对象标识,因为共享对象不能提供唯一的标识

享元模式的优缺点

享元模式的优点

大大减少对象的创建,降低系统的内存,使效率提高。

享元模式的缺点

提高了系统的复杂度,需要分离出外部状态和内部状态,而且外部状态具有固有化的性质,不应该随着内部状态的变化而变化,否则会造成系统的混乱。

享元模式的实现

用 6 个不同样的花朵图案,画很多个随机尺寸和位置的花,首先需要设计并实现可共享的花朵,使用享元模式需要一种享元工厂和一些享元产品 。
首先,每个图案由一个唯一的 FlowerView 的实例来维护。FlowerViewUIImageView 的子类,用它可以绘制一朵花朵图案。

@interface FlowerView : UIImageView 
{
  
}

- (void) drawRect:(CGRect)rect;

@end
@implementation FlowerView

- (void) drawRect:(CGRect)rect
{
  [self.image drawInRect:rect];
}

@end

接着创建一个享元工厂类 FlowerFactoryFlowerFactoryflowerPool 聚合了一个花朵池的引用。flowerPool 是一个保存 FlowerView 的所有实例的数据结构。如果池中没有所请求花朵的类型,就会创建一个新的 FlowerView 的实例。尽管池中对象的类是 FlowerView , 但客户端只要求 FlowerFactory 返回 UIView 的实例。kTotalNumberOfFlowerTypes是所支持花朵类型的总数。

typedef enum 
{
  kAnemone,
  kCosmos,
  kGerberas,
  kHollyhock,
  kJasmine,
  kZinnia,
  kTotalNumberOfFlowerTypes
} FlowerType;

@interface FlowerFactory : NSObject 
{
  @private
  NSMutableDictionary *_flowerPool;
}

- (UIView *) flowerViewWithType:(FlowerType)type;

@end

@implementation FlowerFactory


- (UIView *) flowerViewWithType:(FlowerType)type
{
  // lazy-load a flower pool
  if (_flowerPool == nil)
  {
    _flowerPool = [[NSMutableDictionary alloc] 
                   initWithCapacity:kTotalNumberOfFlowerTypes];
  }
  
  // try to retrieve a flower
  // from the pool
  UIView *flowerView = [_flowerPool objectForKey:[NSNumber 
                                                  numberWithInt:type]];
  
  // if the type requested
  // is not available then
  // create a new one and
  // add it to the pool
  if (flowerView == nil)
  {
    UIImage *flowerImage;
    
    switch (type) 
    {
      case kAnemone:
        flowerImage = [UIImage imageNamed:@"anemone.png"];
        break;
      case kCosmos:
        flowerImage = [UIImage imageNamed:@"cosmos.png"];
        break;
      case kGerberas:
        flowerImage = [UIImage imageNamed:@"gerberas.png"];
        break;
      case kHollyhock:
        flowerImage = [UIImage imageNamed:@"hollyhock.png"];
        break;
      case kJasmine:
        flowerImage = [UIImage imageNamed:@"jasmine.png"];
        break;
      case kZinnia:
        flowerImage = [UIImage imageNamed:@"zinnia.png"];
        break;
      default:
        break;
    } 
    
    flowerView = [[FlowerView alloc] 
                   initWithImage:flowerImage];
    [_flowerPool setObject:flowerView 
                    forKey:[NSNumber numberWithInt:type]];
  }
  
  return flowerView;
}

@end

定义 ExtrinsicFlowerState 数据结构用于保存享元对象的外部状态(显示的位置),并保存对应 FlowerView 的指针。

typedef struct
{
  UIView *flowerView;
  CGRect area;
} ExtrinsicFlowerState;

通过 flowerList 数组保存的每个花朵的外部状态信息 ExtrinsicFlowerState将其绘制在屏幕上

@interface FlyweightView : UIView 
{
  @private
  NSArray *_flowerList;
}

@property (nonatomic, retain) NSArray *flowerList;

@end

@implementation FlyweightView

@synthesize flowerList=_flowerList;

extern NSString *FlowerObjectKey, *FlowerLocationKey;

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect 
{
  // Drawing code
  
  for (NSValue *stateValue in _flowerList)
  {
    ExtrinsicFlowerState state;
    [stateValue getValue:&state];
    
    UIView *flowerView = state.flowerView;
    CGRect area = state.area;
    
    [flowerView drawRect:area];
  }
}

@end

随机生成 500 朵花的位置,通过数组存储这些外部状态,通过享元对象共享内部信息,将其实现。

 FlowerFactory *factory = [[[FlowerFactory alloc] init] autorelease];
  NSMutableArray *flowerList = [[[NSMutableArray alloc] 
                                 initWithCapacity:500] autorelease];
  
  for (int i = 0; i < 500; ++i)
  {
    // retrieve a shared instance 
    // of a flower flyweight object
    // from a flower factory with a
    // random flower type
    FlowerType flowerType = arc4random() % kTotalNumberOfFlowerTypes;
    UIView *flowerView = [factory flowerViewWithType:flowerType];
    
    // set up a location and an area for the flower
    // to display onscreen
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    CGFloat x = (arc4random() % (NSInteger)screenBounds.size.width);
    CGFloat y = (arc4random() % (NSInteger)screenBounds.size.height);
    NSInteger minSize = 10;
    NSInteger maxSize = 50;
    CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;

    // assign attributes for a flower
    // to an extrinsic state object
    ExtrinsicFlowerState extrinsicState;
    extrinsicState.flowerView = flowerView;
    extrinsicState.area = CGRectMake(x, y, size, size);
    
    // add an extrinsic flower state
    // to the flower list
    [flowerList addObject:[NSValue value:&extrinsicState 
                            withObjCType:@encode(ExtrinsicFlowerState)]];
  }
 
  // add the flower list to
  // this FlyweightView instance
  [(FlyweightView *)self.view setFlowerList:flowerList];

总结

    享元模式使用共享技术有效地支持大量细粒度的对象。在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。需要注意的是划分外部状态和内部状态,否则可能会引起线程安全问题,这些类必须有一个工厂对象加以控制。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容