首页 > 代码库 > 纵横之设计模式(享元模式-性能与对象访问)

纵横之设计模式(享元模式-性能与对象访问)

声明:本系列文章内容摘自《iOS设计模式》

享元模式:运用共享技术有效地支持大量细粒度的对象。

何为享元模式

     实现享元模式需要两个关键组件,通常是可共享的享元对象和保存它们的池。某种中央对象维护这个池,并从它返回适当的实例,工厂是这一角色的理想候选。它可以通过一个工厂方法,根据父类型返回各种类型的具体享元对象。其主要目的就是维护池中的享元对象,并适当的从中返回享元对象。

何时使用享元模式

1.应用程序使用很多对象;

2.在内存中保存对象会影响内存性能;

3.对象的多处持有状态(外在状态)可以放到外部而轻量化;

4.移除了外在状态之后,可以用较少的共享对象替代原来的那组对象;

5.应用程序不依赖对象标识,因为共享对象不能提供唯一的标识。

本例通过用6个实例对象完成“百花池”的实现来说明享元模式。

案例说明

     本例中在工厂类中采用懒加载,仅创建6中花色的花朵实例对象。确定花色以后,其外在状态(位置和大小)在图形绘制的方法中实现。本例使用的类主要包括:TheFlowerbedsViewController(百花池控制器)、FlowerView(UIImageView子类)用来绘制一个花朵、FlowerFactory(享元工厂)管理一个FlowerView实例的池,FlowerFactory返回的是一个UIView对象,因为某些时候我们也需要能够绘制单个图形而不是现实固定的图像。

代码说明

FlowerView.h

#import <UIKit/UIKit.h>

@interface FlowerView : UIImageView


-(void)drawRect:(CGRect)rect;

@end

FlowerView.m

#import "FlowerView.h"

@implementation FlowerView


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


@end

 该类仅重载了UIImageView的drawRect:方法。

FlowFactory.h

#import <Foundation/Foundation.h>

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

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

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

 FlowFactory.m

#import "FlowerFactory.h"
#import "FlowerView.h"

@implementation FlowerFactory

-(UIView *)flowerViewWithType:(FlowerType)type
{
    //懒加载花朵池
    if (flowerPool_ == nil) {
        flowerPool_ = [[NSMutableDictionary alloc] initWithCapacity:kTotalNumberOfFlowerTypes];
    }
    //尝试从花朵池中取出一朵花
    UIView *flowerView = [flowerPool_ objectForKey:[NSNumber numberWithInt:type]];
    
    //如果请求的类型不存在,那么就创建一个,并添加到池里
    if (flowerView == nil) {
        
        UIImage *flowerImage;
        switch (type) {
            case kAnemone:
            {
                flowerImage = [UIImage imageNamed:@"flower1"];
            }
                break;
            case kCosmos:
            {
                flowerImage = [UIImage imageNamed:@"flower2"];
            }
                break;
            case kGerberas:
            {
                flowerImage = [UIImage imageNamed:@"flower3"];
            }
                break;
            case kHollyhock:
            {
                flowerImage = [UIImage imageNamed:@"flower4"];
            }
                break;
            case kJasmine:
            {
                flowerImage = [UIImage imageNamed:@"flower5"];
            }
                break;
            case kZinnia:
            {
                flowerImage = [UIImage imageNamed:@"flower6"];
            }
                break;
                
            default:
                break;
        }
        
        flowerView = [[FlowerView alloc] initWithImage:flowerImage];
        [flowerPool_ setObject:flowerView forKey:[NSNumber numberWithInt:type]];
    }
    
    return flowerView;
}

@end

 kTotalNumberOfFlowerTypes是花朵类型的总个数。私有的NSMutableDictionary变量flowerPool_,用来保存整个可供返回花朵的池。它也定义了根据FlowerType参数返回特定UiView的工厂方法。如果实例不在池中,我们将创建一个新的实例添加到池中,如果池中已经存在该实例,就返回池中的实例。

如何共享FlowerView

      在这个模式的原始定义中,想源对象似乎总是和某种可共享的内在状态联系在一起。尽管并不完全如此,但我们的FlowerView享元对象确实共享了作为内在状态的内部花朵图案。 

      每朵花都有各自的显示区域,所以这需要作为外在状态的处理。我们需要一个数组为客户端生成的花朵保存这种信息。下图解释后外在花朵状态的数组如何与FlowerFactory的享元池中的FlowerView独特实例联系起来。

技术分享

     数组中的每个元素保存有一个指针和花朵的显示区域(本例中没有存储显示区域),指针指向池中真正的FlowerView对象,数组的大小和池的大小无关。

能节省多少空间?

通过享元对象能够节省的空间,取决于几个因素:

1.通过共享减少的对象总数;

2.每个对象内在状态(即:可共享的状态)的数量;

3.外在状态是计算出来的还是保存的。

实质是:通过共享减少了内在状态的开销和通过牺牲计算时间又节省了外在状态的存储空间。

控制器代码实现

 

#pragma mark - 创建花圃
-(void)createFlowers
{
    
    //构造花朵列表
    FlowerFactory *factory = [[FlowerFactory alloc] init];
    NSMutableArray *flowerList = [[NSMutableArray alloc] initWithCapacity:500];
    ;
    
    for (int i = 0; i<500; i++) {
        //使用随机花朵类型
        //从花朵工厂取得一个共享的花朵享元对象实例
        FlowerType flowerType = arc4random()%kTotalNumberOfFlowerTypes;
        UIView *flowerView = [factory flowerViewWithType:flowerType];
        
        /*
         * 由于ARC中结构体当中无法添加对象类型(可以查阅原书中的实现),
         * 考虑到本例是用来减少内存消耗的,
         * 所以将计算的本添加到了绘制图形的方法当中
         */
        
        /*
         
         //设置花朵的显示位置和大小
         CGFloat x = (arc4random()%(NSInteger)kDeviceWidth);
         CGFloat y = (arc4random()%(NSInteger)kDeviceHeight);
         NSInteger minSize = 10;
         NSInteger maxSize = 50;
         CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
         
         //拔花朵的属性赋值给一个外在状态对象
         _extrinsicFlowerState = [[ExtrinsicFlowerStateModel alloc] init];
         _extrinsicFlowerState.flowerView = flowerView;
         _extrinsicFlowerState.area = CGRectMake(x, y, size, size);
         [flowerList addObject:_extrinsicFlowerState];//把外在状态添加到列表
         
         */
        
        //把内在花朵状态添加到花朵列表
        [flowerList addObject:flowerView];
    }
    FlyweightView *view = [[FlyweightView alloc] initWithFrame:CGRectMake(0, 64, kDeviceWidth, kDeviceHeight-64)];
    view.backgroundColor = [UIColor colorWithRed:10/255.0 green:80/255.0 blue:10/255.0 alpha:1.0];
    [view setFlowerList:flowerList];
    [self.view addSubview:view];
}

 用于绘制花朵的View

FlyweightView.h
#import <UIKit/UIKit.h>

@interface FlyweightView : UIView

@property (nonatomic, strong) NSMutableArray *flowerList;

@end

FlyweightView.m

#import "FlyweightView.h"
#import "ExtrinsicFlowerStateModel.h"

@implementation FlyweightView

- (void)drawRect:(CGRect)rect
{
    
    /*
     * 使用对象存储的情况
     for (ExtrinsicFlowerStateModel *obj in _flowerList) {
     UIView *flowerView = obj.flowerView;
     CGRect area = obj.area;
     [flowerView drawRect:area];
     }
     */
    
    for (UIView *obj in _flowerList) {
        UIView *flowerView = obj;
        //设置花朵的显示位置和大小
        CGFloat x = (arc4random()%(NSInteger)kDeviceWidth);
        CGFloat y = (arc4random()%(NSInteger)kDeviceHeight);
        NSInteger minSize = 10;
        NSInteger maxSize = 50;
        CGFloat size = (arc4random() % (maxSize - minSize + 1)) + minSize;
        CGRect area = CGRectMake(x, y, size, size);
        [flowerView drawRect:area];
    }
}

原例FlyweightView.m中的代码

技术分享

 

 

 

 

 

 

 

 

原来的控制器实现过程

技术分享

原例中结构体的内容是:

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

 在使用ARC之后结构体当中不能使用对象类型。

 总结

本例中,我们设计了一个可以再屏幕上显示500多花朵的程序,而只用了6个不同花朵图案的实例。这些不同的花朵实例,把一些与众不同的可被标识的信息(位置和大小)去掉,只剩下显示花朵团的基本操作。在请求特定的花朵的时候,可控制器需要像花朵实例提供某些与众不同的信息(外在状态),它让使用这些信息绘制一朵与众不同的花,不用享元模式的话,要在屏幕上创建500个UIImageView实例,享元模式可以通过共享一部分必需的对象,来节省大量内存。

 效果图:

技术分享

Demo地址:https://github.com/haozheMa/DesignPatternsCollections

纵横之设计模式(享元模式-性能与对象访问)