设计模式学习-对象池模式

对象池模式

基本概念

对象池模式一般用来管理一组可重用对象,以供调用组件使用,它可以为组件提供多个完全相同的对象。组件可以从对象池中获取对象,调用对象后,其他组件在该对象想归还前都无法使用该对象。(Cocoa中 UITableViewCell的重用机制可以通过此模式和工厂模式共同实现)

好处

  • 对象构建过程隐藏
  • 对象池通过重用机制有效控制对象反复重建造成的消耗。更好的控制内存

实现过程

  • 初始化,准备需要的对象集合
  • 借出对象,需要对象的组件从池子中借出对象
  • 组件使用借到的对象完成任务,对象池保证这个对象在其被归还之前不会再借给其他组件
  • 组件返回对象给对象池

注意:

1、在多线程访问中保护对象池数据数组

2、确保每次请求都能获得可用对象

代码示例:

下面代码将模拟一个简单的图书管理过程,包含图书出借和归还等。创建一个macos 命令行项目命名为ObjectPool

  • 首先构造 Book类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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
    }
    }
  • 创建Pool类,这里pool类仅代表对象池。为了方便复用,使用泛型创建。以便可以管理任何类型的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Pool.swift
class Pool<T> {
private var data:[T] = []
init(items:[T]) {
data.reserveCapacity(data.count)
data.append(contentsOf: items)
}
func getFromPool() -> T? {
if data.count > 0 {
return data.remove(at: 0)
}
return nil
}
func returnPool(item:T) {
self.data.append(item)
}
}

修改pool类加入线程保护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Pool<T> {
private var data:[T] = []
private let dataProtectQueue = DispatchQueue(label: "com.ra.ObjectPool.Pool.DataProtectQueue")
private let semaphore:DispatchSemaphore
init(items:[T]) {
data.reserveCapacity(data.count)
data.append(contentsOf: items)
self.semaphore = DispatchSemaphore(value: items.count)
}
func getFromPool() -> T? {
var result:T?
if semaphore.wait(timeout: DispatchTime.distantFuture) == .success{
dataProtectQueue.sync {
result = self.data.remove(at: 0)
}
}
return result
}
func returnPool(item:T) {
dataProtectQueue.async {
self.data.append(item)
self.semaphore.signal()
}
}
}
  • 构造library单例类,用于管理图书
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
final class Library{
static let shared:Library = Library(stockLevel: 2)
private var books:[Book] = []
private var pool:Pool<Book>
private init(stockLevel:Int){
for count in 1...stockLevel {
let book = Book(author: "xxxx", title: "Design Pattern in Swift", stock: count)
books.append(book)
}
self.pool = Pool(items: books)
}
func checkoutBook(reader:String) -> Book? {
let book = pool.getFromPool()
book?.reader = reader
book?.checkoutCount += 1
return book
}
func returnBook(_ book:Book) {
book.reader = nil
pool.returnPool(item: book)
}
func printReport() {
books.forEach { (book) in
print("....Book#\(book.stockNumber)....")
print("Checked out to \(book.checkoutCount) times ")
if let reader = book.reader{
print("Checked out to \(reader)")
}else{
print("in stock")
}
}
}
}

然后在main.swift中做一个简单调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let queue = DispatchQueue(label: "work.queue", qos: DispatchQoS.default, attributes: .concurrent)
let group = DispatchGroup()
print("start")
for i in 0..<20{
let workItem = DispatchWorkItem(block: {
if let book = Library.shared.checkoutBook(reader: "reader#\(i)"){
Thread.sleep(forTimeInterval: TimeInterval(arc4random() % 2))
Library.shared.returnBook(book)
}
})
queue.async(group: group, execute: workItem)
}
_ = group.wait(timeout: DispatchTime.distantFuture)
print("all blocks complete")
Library.shared.printReport()

下面是执行结果

1
2
3
4
5
6
7
8
9
10
Hello, World!
start
all blocks complete
....Book#1....
Checked out to 10 times
in stock
....Book#2....
Checked out to 10 times
in stock
Program ended with exit code: 0

对象池模式的变体

基本概念

更改对象池的运作方式来适应不同的场景

对象池实现设计四种策略:

对象创建策略(对象的创建方式)

积极性策略,即对象在使用前就已经被创建(上面示例代码中pool类的初始化方法属于该类型)

缺点:

  • 在需求出现之前就已经花了创建和配置对象所需要的资源
  • 创建和配置的对象有可能与需求不相符即对象不可用

惰性策略,即被动型,需要对象的时候才会被创建

代码示例:

在ObjectPool工程中创建BookSeller类。这里的实现方式只是给需求方提供Book的获取方法。其实现不重要

1
2
3
4
5
class BookSeller {
class func buyBook(author:String,title:String,stockNumber:Int) -> Book{
return Book(author: author, title: title, stock: stockNumber)
}
}

修改pool类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class Pool<T> {
private var data:[T] = []
private let dataProtectQueue = DispatchQueue(label: "com.ra.ObjectPool.Pool.DataProtectQueue")
private let semaphore:DispatchSemaphore
private var itemCount:Int = 0
private let maxItemCount:Int
private let itemFactory:()->T
init(maxItemCount:Int,factory:@escaping ()->T) {
self.itemFactory = factory
self.maxItemCount = maxItemCount
self.semaphore = DispatchSemaphore(value: maxItemCount)
}
func getFromPool() -> T? {
var result:T?
if semaphore.wait(timeout: DispatchTime.distantFuture) == .success{
dataProtectQueue.sync {
if self.data.count == 0 && self.itemCount < self.maxItemCount{
result = self.itemFactory()
self.itemCount += 1
}else{
result = self.data.removeFirst()
}
}
}
return result
}
func returnPool(item:T) {
dataProtectQueue.async {
self.data.append(item)
self.semaphore.signal()
}
}
func processPoolItems(callBack:(([T]) -> Void)) {
dataProtectQueue.sync {
callBack(self.data)
}
}
}

修改Library类中相关初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
///修改初始化方法
private init(stockLevel:Int){
var stockId = 1
stockId += 1
self.pool = Pool(maxItemCount: stockLevel, factory: { () in
return BookSeller.buyBook(author: "Dickens,Charles", title: "Hard times", stockNumber: stockId)
})
}
///修改打印方法
func printReport() {
pool.processPoolItems { (books) in
books.forEach { (book) in
print("....Book#\(book.stockNumber)....")
print("Checked out to \(book.checkoutCount) times ")
if let reader = book.reader{
print("Checked out to \(reader)")
}else{
print("in stock")
}
}
}
}

对象复用策略

对象池模式的本质决定了它所管理的对象会被重复分配给调用组件,这意味着返还的对象会处于非常正常状态的风险

  • 相信策略(默认所有返回对象都是可服用的)
  • 不信任策略(对象返回给对象池之前进行检查。不可用就抛弃)

代码修改:
创建PoolItem.Swift文件。并创建协议

1
2
3
protocol PoolItem {
var canReuse:Bool{ get }
}

修改Pool类以及returnToPool方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Pool<T> where T:AnyObject {
.....
func returnPool(item:T) {
dataProtectQueue.async {
///对归还对象进行检查
if let pitem = item as? PoolItem {
if pitem.canReuse {
self.data.append(item)
self.semaphore.signal()
}
}
}
}
.....
}

Book类遵守并实现协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Book : PoolItem{
let author:String
let title:String
let stockNumber:Int
var reader:String?
var checkoutCount = 0
var canReuse: Bool{
get{
let reusable = checkoutCount < 5
if !reusable {
print("Eject : Book#\(self.stockNumber)")
}
return reusable
}
}
init(author:String,title:String,stock:Int) {
self.author = author
self.title = title
self.stockNumber = stock
}
}

空池策略

对象池中没有对象可满足新的请求时,阻塞请求线程,强制让发起对象请求的线程等待,值到有可用对象后再继续执行

修改main.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 修改为 35次。对象池最多只能返回5个对象。此处修改只为了配合测空池策略
for i in 0..<35{
let workItem = DispatchWorkItem(block: {
if let book = Library.shared.checkoutBook(reader: "reader#\(i)"){
Thread.sleep(forTimeInterval: TimeInterval(arc4random() % 2))
Library.shared.returnBook(book)
}
})
queue.async(group: group, execute: workItem)
}
....
queue.sync {
print("all blocks complete")
Library.shared.printReport()
}

修改pool类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
func getFromPool(maxWaitSecond:Int = 5) -> T? {
var result:T?
let waitTime = (maxWaitSecond == -1) ? DispatchTime.distantFuture : DispatchTime(uptimeNanoseconds: UInt64(maxWaitSecond*Int(NSEC_PER_SEC)))
if semaphore.wait(timeout: waitTime) == .success{
dataProtectQueue.sync {
if self.data.count == 0 && self.itemCount < self.maxItemCount{
result = self.itemFactory()
self.itemCount += 1
}else{
result = self.data.removeFirst()
}
}
}
return result
}
...
  • 弹性对象

此处修改见 demo ObjectPool(弹性对象)

对象分配策略

  • 先进先出
  • 优先分配使用最少的