Objective-C设计模式解析-享元

看图识模式

一. 公交bus

需求:

有三我的分别要去A、B、C三个地方,因为目的地比较远,他们分别驾驶了三辆车到达目的地。 编程

clipboard.png

问题

若是要去100个目的地,每一个目的地派遣1辆车,那么总计须要100辆车。
好比北上广深等一线城市天天几千万次出行,没人一辆车城市不就瘫痪了嘛。。。。设计模式

因此,如何解决呢?数组

分析

经过分析,其实咱们不难发现2个规律:服务器

1. 去任意地方的交通工具是相同的
2. 不一样的是目的地位置

方案

clipboard.png

经过开设公共交通来设置多个车站,让大量去往相同方向的乘客分担保有和经营车辆的费用。
乘客沿着路线在接近他们目的地的地方上下车。达到目的地的费用仅与行程有关。
跟保有车辆相比,乘坐公共交通更便宜,这就是利用公共资源的好处。数据结构

二. 棋牌类游戏设计(围棋)

clipboard.png

围棋棋盘理论上有361个空位能够放棋子,那若是用常规的面向对象方式编程,每局棋可能有两三百个棋子对象产生。
若是有一台服务器为玩家提供在线游戏,这样可能很难支持更多的玩家同时参与了,毕竟内存空间是有限的dom

分析

  1. 咱们发现每个棋子其实本质上是同样的,颜色(只有黑、白)也是不常变化的。
  2. 而每个棋子的位置是不固定的。

一个完整的棋子包括: 棋子自己(黑/白) + 棋盘坐标工具

同理

咱们只要2个棋子就够了,在外部保存一个数组存储坐标信息。
使用时,咱们只需根据key(黑、白)取到对应的棋子,而后把坐标信息传递给棋子,这样咱们就获得对应完整棋子对象。性能

模式定义

具体看一下享元模式是如何描述的:atom

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

结构图

先看一下静态类图:spa

clipboard.png

在享元模式结构图中包含以下几个角色:

  • Flyweight(抽象享元类): 一般是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法能够向外界提供享元对象的内部数据(内部状态),同时也能够经过这些方法来设置外部数据(外部状态)。
  • ConcreteFlyweight(具体享元类): 它实现了抽象享元类,其实例称为享元对象;在具体享元类中为内部状态提供了存储空间。一般咱们能够结合单例模式来设计具体享元类,为每个具体享元类提供惟一的享元对象。
  • FlyweightFactory(享元工厂类): 享元工厂类用于建立并管理享元对象,它针对抽象享元类编程,将各类类型的具体享元对象存储在一个享元池中,享元池通常设计为一个存储“键值对”的集合(也能够是其余类型的集合),能够结合工厂模式进行设计;当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已建立的实例或者建立一个新的实例(若是不存在的话),返回新建立的实例并将其存储在享元池中。

应用示例

这里以iOS的一个小项目为例:

在屏幕上随机显示花朵图案,图案的类型、位置、大小都是随机生成,并且让这些花朵可以扑满屏幕。

下面是给出的花朵样式:

clipboard.png

随机生成200个花朵图案的效果大约是这样:

clipboard.png

目标

这个项目的目标是用6个不一样的实例,绘制不少(成百或更多)随机尺寸和位置的花。若是为屏幕上所画的梅朵花建立一个实例,程序就会占用不少内存。
因此,咱们使用享元模式来限制花朵实例的数量,让它很少于可选花朵类型的总数。

设计

上面给出了6种花朵图案,让每一种图案由一个惟一的FlowerView的实例来维护,而与须要的花朵总数无关。下面是它们的静态关系图:

clipboard.png

FlowerView

FlowerView是UIImageView的子类,用它绘制一朵花朵图案。FlowerFactory是享元工厂类,它管理一个FlowerView实例的池。

尽管工厂池中对象是FlowerView,但要求FlowerFactory返回UIView的实例。与让工厂直接返回UIImage类型的最终产品相比,这样的设计更灵活。由于有时候咱们可能须要自定义绘制其它的图案,而不仅是显示固定的图像。

UIView是任何须要在屏幕上绘图的最高抽象。FlowerFactory能够返回任何UIView子类对象,而不会破坏系统。这就是针对接口编程,而不是针对实现编程的好处。

FlowerFactory

FlowerFactory用flowerPool聚合了一个花朵池的引用。flowerPool是一个保存FlowerView的全部实例的数据结构。

FlowerFactory经过flowerViewWithType: 方法返回FlowerView实例。

若是池子中没有所请求花朵的类型,就会建立一个新的FlowView实例返回,并保存到池子中。

代码

FlowerView:

它是UIImageView的子类,代码比较简单

@interface FlowerView : UIImageView

@end

@implementation FlowerView


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


@end

FlowerFactory

typedef NS_ENUM(NSInteger, FlowerType) {
    kAnemone = 0,
    kCosmos,
    kGerberas,
    kHollyhock,
    kJasmine,
    kZinnia,
    kTotalNumberOfFlowTypes,
};

@interface FlowerFactory : NSObject

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

@end

@interface FlowerFactory ()

@property (nonatomic, strong) NSMutableDictionary *flowerPool;

@end

@implementation FlowerFactory

- (UIView *)flowerViewWithType:(FlowerType)type
{
    FlowerView *flowerView = [self.flowerPool objectForKey:@(type)];
    if (nil == flowerView) {
        UIImage *image = nil;
        switch (type) {
            case kAnemone:
                image = [UIImage imageNamed:@"anemone"];
                break;
            case kCosmos:
                image = [UIImage imageNamed:@"cosmos"];
                break;
            case kGerberas:
                image = [UIImage imageNamed:@"gerberas"];
                break;
            case kHollyhock:
                image = [UIImage imageNamed:@"hollyhock"];
                break;
            case kJasmine:
                image = [UIImage imageNamed:@"jasmine"];
                break;
            case kZinnia:
                image = [UIImage imageNamed:@"zinnia"];
                break;
                
            default:
                break;
        }
        flowerView = [[FlowerView alloc] init];
        flowerView.image = image;
        [self.flowerPool setObject:flowerView forKey:@(type)];
    }
    
    return  flowerView;
}

- (NSMutableDictionary *)flowerPool
{
    if (_flowerPool == nil) {
        _flowerPool = [NSMutableDictionary dictionaryWithCapacity:7];
    }
    return _flowerPool;
}

@end

ExtrinsicFlowerState.h

享元对象老是和某种可共享的内在状态联系在一块儿,尽管并不彻底如此,可是咱们的FlowerView享元对象确实共享了做为其内在状态的内部花朵图案。无论享元对象是否有可供共享的内在状态,任然须要定义某种外部的数据结构,保存享元对象的外在状态。

每朵花都有各自的显示区域,因此这须要做为外在状态来处理。

这里特此定义了一个C结构体ExtrinsicFlowerState。

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

FlowerContainerView

咱们把最终生成的全部花朵实例都填充到FlowerContainerView视图上

@interface FlowerContainerView : UIView

@property (nonatomic, copy) NSArray *flowerList;

@end

@implementation FlowerContainerView


- (void)drawRect:(CGRect)rect {
    
    for (NSValue *stateValue in self.flowerList) {
        struct ExtrinsicFlowerState flowerState;
        [stateValue getValue:&flowerState];

        UIView *flowerView = flowerState.flowerView;
        CGRect frame = flowerState.area;

        [flowerView drawRect:frame];
    }
}

@end

客户端调用

在rootViewController的viewDidLoad 方法中调用

#define kFlowerListCount 200

@interface ViewController ()

@property (nonatomic, strong) FlowerFactory *flowerFactory;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    FlowerContainerView *flowerContainer = [[FlowerContainerView alloc] initWithFrame:self.view.bounds];
    flowerContainer.backgroundColor = [UIColor whiteColor];
    [self.view addSubview:flowerContainer];
    
    FlowerFactory *factory = [[FlowerFactory alloc] init];
    self.flowerFactory = factory;
    NSMutableArray *flowerList = [NSMutableArray arrayWithCapacity:kFlowerListCount];;
    
    for (NSInteger i = 0; i < kFlowerListCount; i++) {
        
        // 从花朵工厂取得一个共享的花朵享元对象实例
        FlowerType type = arc4random() % kTotalNumberOfFlowTypes;
        UIView *flowerView = [factory flowerViewWithType:type];
        
        // 设置花朵的显示位置和区域
        CGRect screenBounds = [[UIScreen mainScreen] bounds];
        CGFloat x = arc4random() % (NSInteger)screenBounds.size.width;
        CGFloat y = arc4random() % (NSInteger)screenBounds.size.height;
        NSInteger minSize = 10;
        NSInteger maxSize = 60;
        CGFloat size = (arc4random() % (maxSize - minSize)) + minSize;
        
        // 把花朵参数存入一个外在对象
        struct ExtrinsicFlowerState extrinsicState;
        extrinsicState.flowerView = flowerView;
        extrinsicState.area = CGRectMake(x, y, size, size);
        
        // 花朵外在状态添加到花朵列表中
        [flowerList addObject:[NSValue value:&extrinsicState withObjCType:@encode(struct ExtrinsicFlowerState)]];
    }
    [flowerContainer setFlowerList:flowerList];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

运行的结果

clipboard.png

源码下载地址FlyweightDemo

特征&总结

模式分析

享元模式是一个考虑系统性能的设计模式,经过使用享元模式能够节约内存空间,提升系统的性能。

享元模式的核心在于享元工厂类,享元工厂类的做用在于提供一个用于存储享元对象的享元池,用户须要对象时,首先从享元池中获取,若是享元池中不存在,则建立一个新的享元对象返回给用户,并在享元池中保存该新增对象。

享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能作到共享的关键是区份内部状态(Internal State)和外部状态(External State)。

  • 内部状态是存储在享元对象内部而且不会随环境改变而改变的状态,所以内部状态能够共享。
  • 外部状态是随环境改变而改变的、不能够共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被建立以后,在须要使用的时候再传入到享元对象内部。一个外部状态与另外一个外部状态之间是相互独立的。

适用环境

在如下状况下可使用享元模式:

  • 一个系统有大量相同或者类似的对象,因为这类对象的大量使用,形成内存的大量耗费。
  • 对象的大部分状态均可之外部化,能够将这些外部状态传入对象中。
  • 使用享元模式须要维护一个存储享元对象的享元池,而这须要耗费资源,所以,应当在屡次重复使用享元对象时才值得使用享元模式。

优势 & 缺点

优势

享元模式的优势

  • 享元模式的优势在于它能够极大减小内存中对象的数量,使得相同对象或类似对象在内存中只保存一份。
  • 享元模式的外部状态相对独立,并且不会影响其内部状态,从而使得享元对象能够在不一样的环境中被共享。

缺点

享元模式的缺点

  • 享元模式使得系统更加复杂,须要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
  • 为了使对象能够共享,享元模式须要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
相关文章
相关标签/搜索