创建者模式-对象池模式(The Object Pool Pattern)

本文大部分内容翻译至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些许修改,并将代码升级到了Swift2.0,翻译不当之处望多包涵。

对象池模式(The Object Pool Pattern)

对象池模式是单例模式的一个变种,它提供了获取一系列相同对象实例的入口。当你需要对象来代表一组可替代资源的时候就变的很有用,每个对象每次可以被一个组件使用。


理解对象池模式解决的问题

在许多项目中,有时候对象的实例数目可能会有限制。请看下面例子:

Book.swift

import Foundation
class Book {
    
    let author:String
    let title:String
    let stockNumber:Int
    var reader:String?
    var checkoutCount = 0
    
    init(author:String, title:String, stock:Int) {
    self.author = author
    self.title = title
    self.stockNumber = stock
    }
}

在一个追踪图书馆书的系统中,创建或者克隆Book对象都不适用于现实中的图书馆中的书。同样的如果使用单例模式也不行,因为图书馆里可不止一本书。
图书管中的每一本书都有可能在某个时候被读者借出并且以后不能再被人使用直到归还。当书在库的时候读者可以立即借出,但是当库存耗尽的时候,任何想要再借这本书的人都必须等到某个人还书或者图书馆增加库存。


理解对象池模式

对象池模式管理一个可代替对象的集合。组件从池中借出对象,用它来完成一些任务并当任务完成时归还该对象。被归还的对象接着满足请求,不管是同一个组件还是其他组件的请求。对象池模式可以管理那些代表的现实资源或者通过重用来分摊昂贵初始化代价的对象。



第二步操作就是借出。
第三步操作是组件用借出的对象来完成一些任务。这时候并不需要对象池再做什么,但是这也意味着该对象将被租借一段时间并且不能在被其他组件借出。
第四步操作就是归还,组件归还借出的对象这样可以继续满足其他的租借请求。

在一个多线程的应用中,第二,第三,第四步操作都有可能发生并发操作。多线程的组件中分享对象导致了潜在的并发问题。

也存在一种情况就是当所有对象都被借出时不能满足接下来的请求,对象池必须应对这些请求,不管是告诉组件已经没有对象可借还是允许组件等待直到有归还的对象。


实现对象池模式

  • 定义对象池类

首先是定义一个对象池的泛型类,这倒不一定非得是泛型,只是说利用泛型可以更好的重用代码。

Pool.swift

class Pool<T> {
    private var data = [T]()
    
    init(items:[T]) {
        
       data.reserveCapacity(items.count)
        
       for item in items {
        
           data.append(item)
        }
    
    }
    
    func getFromPool() -> T? {
       var result:T?
    
       if (data.count > 0) {
          result = self.data.removeAtIndex(0)
        }
        return result
    }
    
    func returnToPool(item:T) {
        self.data.append(item)
    }
}

这个Pool类,更准确的说是Pool<T>类,用被管理的对象的集合来初始化。初始化方法将传入的数组元素复制到局部声明的数组里。当getFromPool被调用时,我们将数组第一个对象从数组中移除并返回。returnToPool方法调用了数组到append方法将借出的对象返还给对象数组池。

  • 保护对象池数组

处理并发问题对于对象池来说十分重要,这里有两个问题我们需要解决。遇到的第一个问题和单例模式遇到的一样,getFromPool方法和returnToPool方法都操作了数组,我们需要确保当两个线程同时调用时不会出问题。

Pool.swift

import Foundation

class Pool<T> {
    private var data = [T]()
    private let queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
    
    init(items:[T]) {
        
       data.reserveCapacity(items.count)
        
       for item in items {
        
           data.append(item)
        }
    
    }
    
    func getFromPool() -> T? {
       var result:T?
    
       if (data.count > 0) {
           dispatch_sync(queue, { () -> Void in
                result = self.data.removeAtIndex(0)
            })
        
        }
        return result
    }
    
    func returnToPool(item:T) {
        dispatch_async(queue) { () -> Void in
           self.data.append(item)
        }
        
    }
}
  • 确保对象能借出

在Pool类中其实还有第二个并发问题。在getFromPool方法中,我们检查了在对象池中的对象是否为0。

Pool.swift

...
  if (data.count > 0) {
           dispatch_sync(queue, { () -> Void in
                result = self.data.removeAtIndex(0)
            })
        
        }
...

这是一个经典的并发问题。想象一下对象池中只剩下了一个对象,但此时两个线程在极短的时间间隔下去调用getFromPool 方法。第一个线程检查了data.count发现不为0后调用 dispatch_sync并获取对象。
极短时间内,第二个线程做了相同的事情。相信还有一个对象存在,但是当它试图获取这个对象的时候,线程池却是空的。

我们同样用GCD来解决这个问题:

Pool.swift

import Foundation

class Pool<T> {
    private var data = [T]()
    private let queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
    private let semaphore:dispatch_semaphore_t
    
    init(items:[T]) {
        
       data.reserveCapacity(items.count)
        
       for item in items {
        
           data.append(item)
        }
        
        semaphore = dispatch_semaphore_create(items.count)
    
    }
    
    func getFromPool() -> T? {
       var result:T?
    
       if (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) == 0) {
           dispatch_sync(queue, { () -> Void in
                result = self.data.removeAtIndex(0)
            })
        
        }
        return result
    }
    
    func returnToPool(item:T) {
        dispatch_async(queue) { () -> Void in
           self.data.append(item)
           dispatch_semaphore_signal(self.semaphore)
        }
        
    }
}

消费对象池

现在我们已经创建好了一个泛型的对象池。我们可以创建图书馆了。

Library.swift

import Foundation

class Library {
    
    private var books:[Book]
    private let pool:Pool<Book>
    static let sharedInstance = Library(stockLevel: 2)
    
    private init(stockLevel:Int) {
        books = [Book]()
        
        for count in 1 ... stockLevel {
            books.append(Book(author: "Dickens, Charles", title: "Hard Times",
            stock: count))
      }
        pool = Pool<Book>(items:books)
    }
    
   
    func checkoutBook(reader:String) -> Book? {
        let book = pool.getFromPool()
        book?.reader = reader
        book?.checkoutCount++
        return book
    }
    
    func returnBook(book:Book) {
        book.reader = nil
        pool.returnToPool(book)
    }
    
    func printReport() {
            
        for book in books {
            print("...Book#\(book.stockNumber)...")
            print("Checked out \(book.checkoutCount) times")
            
            if (book.reader != nil) {
                print("Checked out to \(book.reader!)")
            } else {
                print("In stock")
            }
        }
    }
}

Library类通过结合Pool类实现了对象池模式。同时Library我们做成了单例,因为这里我们只有一个图书馆。注意到这里我们将Library类和Book类定义在了不同的文件,但我们却没有保护Book类使得它可以在Library类以外实例化。
接着我们模拟并发借书还书过程:

main.swift

import Foundation

var queue = dispatch_queue_create("workQ", DISPATCH_QUEUE_CONCURRENT)
var group = dispatch_group_create()
print("Starting...")
for i in 1 ... 20 {
    dispatch_group_async(group, queue, {() in
        var book = Library.sharedInstance.checkoutBook("reader#\(i)")
        if (book != nil) {
            NSThread.sleepForTimeInterval(Double(rand() % 2))
            Library.sharedInstance.returnBook(book!)
        }
    })
}

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

print("All blocks complete")

Library.sharedInstance.printReport()

如果我们执行代码,可能得到下面结果(因为 NSThread.sleepForTimeInterval的缘故,结果可能会有所不同,但是借书总次数是一样的20次):

Starting...
All blocks complete
...Book#1...
Checked out 7 times
In stock
...Book#2...
Checked out 13 times
In stock

Cocoa中的对象池模式

Cocoa在公开的API里并没有暴露对象池,除了一个例外:table cell对象。

...
let cell = tableView.dequeueReusableCellWithIdentifier("ProductCell")
 as ProductTableCell
...

这是一个请求获取一个ProductTableCell对象的方法。dequeueReusableCellWithIdentifier方法结合了对象池模式和工厂模式。UIKit框架负责管理UITableViewCell的创建和分配,使得它们可以重用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容