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

浏览: 97 发布日期: 2017-12-21 分类: objective-c

看图识模式

一. 公交bus

需求:

有三个人分别要去A、B、C三个地方,由于目的地比较远,他们分别驾驶了三辆车到达目的地。

问题

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

所以,如何解决呢?

分析

通过分析,其实我们不难发现2个规律:

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

方案

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

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

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

分析

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

一个完整的棋子包括: 棋子本身(黑/白) + 棋盘坐标

同理

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

模式定义

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

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

结构图

先看一下静态类图:

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

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

应用示例

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

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

下面是给出的花朵样式:

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

目标

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

设计

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

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

运行的结果

源码下载地址FlyweightDemo

特征&总结

模式分析

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

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

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

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

适用环境

在以下情况下可以使用享元模式:

  • 一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式。

优点 & 缺点

优点

享元模式的优点

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

缺点

享元模式的缺点

  • 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
  • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
返回顶部