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)。

  • 內部狀態是存儲在享元對象內部而且不會隨環境改變而改變的狀態,所以內部狀態能夠共享。
  • 外部狀態是隨環境改變而改變的、不能夠共享的狀態。享元對象的外部狀態必須由客戶端保存,並在享元對象被建立以後,在須要使用的時候再傳入到享元對象內部。一個外部狀態與另外一個外部狀態之間是相互獨立的。

適用環境

在如下狀況下可使用享元模式:

  • 一個系統有大量相同或者類似的對象,因爲這類對象的大量使用,形成內存的大量耗費。
  • 對象的大部分狀態均可之外部化,能夠將這些外部狀態傳入對象中。
  • 使用享元模式須要維護一個存儲享元對象的享元池,而這須要耗費資源,所以,應當在屢次重複使用享元對象時才值得使用享元模式。

優勢 & 缺點

優勢

享元模式的優勢

  • 享元模式的優勢在於它能夠極大減小內存中對象的數量,使得相同對象或類似對象在內存中只保存一份。
  • 享元模式的外部狀態相對獨立,並且不會影響其內部狀態,從而使得享元對象能夠在不一樣的環境中被共享。

缺點

享元模式的缺點

  • 享元模式使得系統更加複雜,須要分離出內部狀態和外部狀態,這使得程序的邏輯複雜化。
  • 爲了使對象能夠共享,享元模式須要將享元對象的狀態外部化,而讀取外部狀態使得運行時間變長。
相關文章
相關標籤/搜索