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];

总结

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

你可能感兴趣的:(iOS 设计模式的应用 ⑲ 享元模式)