Object Pool

Object pool pattern is also known as resources pool pattern typically reuse an existing objects from the pool. This pattern is highly suitable for those with high reusability expensive object instantiation, such as data structure coming massive database query. Hence, instead of discarding the object after use, the object is saved into the pool for later reuse. The diagram is as follows:

When to Use (Problem)

  • When the instantiation of the object is too costly to be created at a given time
  • When the demands for speed performance is needed.
  • When the data structure is commonly same and frequently requested.

Example (Solution)

Here is an example of a caching implementation in Go:

package main

import (
        "fmt"
        "sync"
        "time"
)

const (
        Table  = "table"
        Chair  = "chair"
        Laptop = "laptop"
)

// CachePool is the resource pool to save the data
type CachePool struct {
        data    map[string]interface{}
        maxPool uint
        lock    sync.RWMutex
}

func NewCachePool() *CachePool {
        return &CachePool{
                data:    make(map[string]interface{}),
                maxPool: 10,
                lock:    sync.RWMutex{},
        }
}

func (c *CachePool) Get(key string) interface{} {
        c.lock.RLock()
        defer c.lock.RUnlock()
        data := c.data[key]
        return data
}

func (c *CachePool) Set(key string, value interface{}) {
        c.lock.Lock()
        defer c.lock.Unlock()
        c.data[key] = value
}

// Operator is a normal office assistant managing the office furniture
type Operator struct {
        warehouse *CachePool
}

func NewOperator() *Operator {
        return &Operator{
                warehouse: NewCachePool(),
        }
}

func (o *Operator) Query() {
        x := o.warehouse.Get(Table)
        y := o.warehouse.Get(Chair)
        z := o.warehouse.Get(Laptop)

        t := uint(0)
        if x != nil {
                t = x.(uint)
        }

        c := uint(0)
        if y != nil {
                c = y.(uint)
        }

        l := uint(0)
        if z != nil {
                l = z.(uint)
        }

        fmt.Printf("operator check warehouse has %v of %s, ", t, Table)
        fmt.Printf("%v of %s, ", c, Chair)
        fmt.Printf("%v of %s.\n", l, Laptop)
}

func (o *Operator) Checkout(unit uint, key string) uint {
        o.Query()
        x := uint(0)
        data := o.warehouse.Get(key)
        if data != nil {
                x = data.(uint)
        }

        if unit > x {
                d := unit - x
                fmt.Printf("operator found shortage of %d for %s. Buying.\n",
                        d,
                        key)
                o.Buy(d, key)
                data := o.warehouse.Get(key)
                if data != nil {
                        x = data.(uint)
                }
        }

        x -= unit
        fmt.Printf("operator checked out %s. Left: %d.\n", key, x)
        o.warehouse.Set(key, x)
        o.Query()
        return x
}

func (o *Operator) Buy(unit uint, key string) {
        var x interface{}
        var i uint

        switch key {
        case Table:
                x = o.warehouse.Get(Table)
        case Chair:
                x = o.warehouse.Get(Chair)
        case Laptop:
                x = o.warehouse.Get(Laptop)
        default:
                goto done
        }

        i = 0
        if x != nil {
                i = x.(uint)
        }
        fmt.Printf("operator purchases %d units of %s.\n", unit, key)
        i += unit
        o.warehouse.Set(key, i)
        o.Query()

done:
        // Operator takes her time to go for shopping!
        time.Sleep(5 * time.Second)
        fmt.Printf("operator spent 5 seconds for some side-way shopping...\n")
}

// The office manager
func main() {
        // manager hired a new operator assistant
        fmt.Printf("manager hired new operator - Alia.\n")
        operator := NewOperator()

        // manager ask operator to buy office furniture
        fmt.Printf("\n\nWEEK 1\n")
        fmt.Printf("manager asked to buy 20 tables, 10 chairs, 5 laptops.\n")
        operator.Buy(20, Table)
        operator.Buy(10, Chair)
        operator.Buy(5, Laptop)

        // manager hired new members
        fmt.Printf("\n\nWEEK 24\n")
        fmt.Printf("manager checkouts 10 tables, 5 chairs, 3 laptops.\n")
        operator.Checkout(10, Table)
        operator.Checkout(5, Chair)
        operator.Checkout(3, Laptop)

        // manager hired another few members
        fmt.Printf("\n\nWEEK 32\n")
        fmt.Printf("manager checkouts 15 tables, 7 chairs, 7 laptops.\n")
        operator.Checkout(15, Table)
        operator.Checkout(7, Chair)
        operator.Checkout(7, Laptop)
}

// Output:
// manager hired new operator - Alia.
//
//
// WEEK 1
// manager asked to buy 20 tables, 10 chairs, 5 laptops.
// operator purchases 20 units of table.
// operator check warehouse has 20 of table, 0 of chair, 0 of laptop.
// operator spent 5 seconds for some side-way shopping...
// operator purchases 10 units of chair.
// operator check warehouse has 20 of table, 10 of chair, 0 of laptop.
// operator spent 5 seconds for some side-way shopping...
// operator purchases 5 units of laptop.
// operator check warehouse has 20 of table, 10 of chair, 5 of laptop.
// operator spent 5 seconds for some side-way shopping...
//
//
// WEEK 24
// manager checkouts 10 tables, 5 chairs, 3 laptops.
// operator check warehouse has 20 of table, 10 of chair, 5 of laptop.
// operator checked out table. Left: 10.
// operator check warehouse has 10 of table, 10 of chair, 5 of laptop.
// operator check warehouse has 10 of table, 10 of chair, 5 of laptop.
// operator checked out chair. Left: 5.
// operator check warehouse has 10 of table, 5 of chair, 5 of laptop.
// operator check warehouse has 10 of table, 5 of chair, 5 of laptop.
// operator checked out laptop. Left: 2.
// operator check warehouse has 10 of table, 5 of chair, 2 of laptop.
//
//
// WEEK 32
// manager checkouts 15 tables, 7 chairs, 7 laptops.
// operator check warehouse has 10 of table, 5 of chair, 2 of laptop.
// operator found shortage of 5 for table. Buying.
// operator purchases 5 units of table.
// operator check warehouse has 15 of table, 5 of chair, 2 of laptop.
// operator spent 5 seconds for some side-way shopping...
// operator checked out table. Left: 0.
// operator check warehouse has 0 of table, 5 of chair, 2 of laptop.
// operator check warehouse has 0 of table, 5 of chair, 2 of laptop.
// operator found shortage of 2 for chair. Buying.
// operator purchases 2 units of chair.
// operator check warehouse has 0 of table, 7 of chair, 2 of laptop.
// operator spent 5 seconds for some side-way shopping...
// operator checked out chair. Left: 0.
// operator check warehouse has 0 of table, 0 of chair, 2 of laptop.
// operator check warehouse has 0 of table, 0 of chair, 2 of laptop.
// operator found shortage of 5 for laptop. Buying.
// operator purchases 5 units of laptop.
// operator check warehouse has 0 of table, 0 of chair, 7 of laptop.
// operator spent 5 seconds for some side-way shopping...
// operator checked out laptop. Left: 0.
// operator check warehouse has 0 of table, 0 of chair, 0 of laptop.

Notice that the operator always take 5 seconds of side-way shopping per item purchases. That is a very long purchasing time to create office furniture objects for the managers. Hence, if the manager purchases upfront and cached them into the warehouse, operator can always refers to warehouse availability. If there are sufficient spares units, operator would not waste too much time on shopping again.

Take a look at Week 24 checkout, it is a lot faster than Week 1 and Week 32 thanks to the warehouse caching mechanism.

Expected Outcome (Consequences)

  1. Operation goes faster if caches fulfill the demand on runtime.
  2. Larger memory consumption for caching storage.
  3. Requires caching policy (when to update, when to delete, when to clean).