Table of Contents
Overview
The Object Pool Design Pattern can be used to design an Object Pool. It is a creational design pattern in which a pool of objects is initialized and created beforehand and kept in a pool. As and when needed, a client can request an object from the pool, use it, and return it to the pool. The object in the pool is never destroyed. In this tutorial, we will look at
- When we want to create a object pool
- UML diagram for the design
- Low-Level Design
- Full Working Code
When we want to create a object pool
- When the cost to create the object of the class is high and the number of such objects that will be needed at a particular time is not much.
-Let’s take the example of DB connections. Each of the connection object creation is the cost is high as there is network calls involved and also at a time not more than a certain connection might be needed. The object pool design pattern is perfectly suitable for such cases.
- When the pool object is the immutable object
-Again take the example of DB connection again. A DB connection is an immutable object. Almost none of its property needs to be changed
- For performance reasons. It will boost the application performance significantly since the pool is already created
UML Diagram
Below is the UML diagram of the Object Pool Design
Here is the overall idea
- We have a Pool Class that does the management of Pool Objects. The Pool class is first initialized with a set of an already created fixed number of pool objects. Then it supports lending, receiving back, and removing of pool objects.
- There is an interface iPoolOjbect that represents the type of objects that will reside in the Pool. There will be different implementations of this interface depending upon the type of use case. For example, in the case of DB Connections, a DB connection will implement this iPoolObject interface.
- There is a Client class that uses Pool class to loan a Pool Object. When it is done with the Pool Object, it returns it to the pool.
Low–Level Design
Below is the low-level design expressed in the Go programming language. Later we will see a working example as well
iPoolObject Interface
type iPoolObject interface {
getID() string
doSomething()
}
DBConnection Class
type dbconnection struct {
id string
}
func (c *dbconnection) getID() string
func (c *dbconnection) doSomething()
Pool Class
type pool struct {
idle []iPoolObject
active []iPoolObject
capacity int
mulock *sync.Mutex
}
//InitPool Initialize the pool
func initPool(poolObjects []iPoolObject) (*pool, error) {}
func (p *pool) loan() (iPoolObject, error) {}
func (p *pool) receive(target iPoolObject) error {}
func (p *pool) remove(target iPoolObject) error {}
func (p *pool) setMaxCapacity(capacity int){}
Client Class
type client struct {
pool *pool
}
func (c *client) init() {}
func (c *client) doWork() {}
Program
Here is the full working code if anyone is interested in the Go programming language
iPoolObject.go
package main
type iPoolObject interface {
getID() string //This is any id which can be used to compare two different pool objects
doSomething()
}
dbconnection.go
package main
import "fmt"
type dbconnection struct {
id string
}
func (c *dbconnection) getID() string {
return c.id
}
func (c *dbconnection) doSomething() {
fmt.Printf("Connection with id %s in action\n", c.getID())
}
pool.go
package main
import (
"fmt"
"sync"
)
type pool struct {
idle []iPoolObject
active []iPoolObject
capacity int
mulock *sync.Mutex
}
//InitPool Initialize the pool
func initPool(poolObjects []iPoolObject) (*pool, error) {
if len(poolObjects) == 0 {
return nil, fmt.Errorf("Cannot craete a pool of 0 length")
}
active := make([]iPoolObject, 0)
pool := &pool{
idle: poolObjects,
active: active,
capacity: len(poolObjects),
mulock: new(sync.Mutex),
}
return pool, nil
}
func (p *pool) loan() (iPoolObject, error) {
p.mulock.Lock()
defer p.mulock.Unlock()
if len(p.idle) == 0 {
return nil, fmt.Errorf("No pool object free. Please request after sometime")
}
obj := p.idle[0]
p.idle = p.idle[1:]
p.active = append(p.active, obj)
fmt.Printf("Loan Pool Object with ID: %s\n", obj.getID())
return obj, nil
}
func (p *pool) receive(target iPoolObject) error {
p.mulock.Lock()
defer p.mulock.Unlock()
err := p.remove(target)
if err != nil {
return err
}
p.idle = append(p.idle, target)
fmt.Printf("Return Pool Object with ID: %s\n", target.getID())
return nil
}
func (p *pool) remove(target iPoolObject) error {
currentActiveLength := len(p.active)
for i, obj := range p.active {
if obj.getID() == target.getID() {
p.active[currentActiveLength-1], p.active[i] = p.active[i], p.active[currentActiveLength-1]
p.active = p.active[:currentActiveLength-1]
return nil
}
}
return fmt.Errorf("Targe pool object doesn't belong to the pool")
}
func (p *pool) setMaxCapacity(capacity int) {
p.capacity = capacity
}
client.go
package main
import (
"fmt"
"log"
"strconv"
)
type client struct {
pool *pool
}
func (c *client) init() {
connections := make([]iPoolObject, 0)
for i := 0; i < 3; i++ {
c := &dbconnection{id: strconv.Itoa(i)}
connections = append(connections, c)
}
var err error
c.pool, err = initPool(connections)
if err != nil {
log.Fatalf("Init Pool Error: %s", err)
}
}
func (c *client) doWork() {
fmt.Printf("Capacity: %d\n\n", c.pool.capacity)
conn1, err := c.pool.loan()
if err != nil {
log.Fatalf("Pool Loan Error: %s", err)
}
conn1.doSomething()
fmt.Printf("InUse: %d\n\n", c.pool.inUse)
conn2, err := c.pool.loan()
if err != nil {
log.Fatalf("Pool Loan Error: %s", err)
}
conn2.doSomething()
fmt.Printf("InUse: %d\n\n", c.pool.inUse)
c.pool.receive(conn1)
fmt.Printf("InUse: %d\n\n", c.pool.inUse)
c.pool.receive(conn2)
fmt.Printf("InUse: %d\n", c.pool.inUse)
}
main.go
package main
func main() {
client := &client{}
client.init()
client.doWork()
}
Output
Capacity: 3
Loan Pool Object with ID: 0
Connection with id 0 in action
InUse: 1
Loan Pool Object with ID: 1
Connection with id 1 in action
InUse: 2
Return Pool Object with ID: 0
InUse: 1
Return Pool Object with ID: 1
InUse: 0
Full Working Code:
Here is the full working code in one file
main.go
package main
import (
"fmt"
"log"
"strconv"
"sync"
)
type pool struct {
idle []iPoolObject
active []iPoolObject
capacity int
inUse int
mulock *sync.Mutex
}
//InitPool Initialize the pool
func initPool(poolObjects []iPoolObject) (*pool, error) {
if len(poolObjects) == 0 {
return nil, fmt.Errorf("Cannot craete a pool of 0 length")
}
active := make([]iPoolObject, 0)
pool := &pool{
idle: poolObjects,
active: active,
capacity: len(poolObjects),
mulock: new(sync.Mutex),
}
return pool, nil
}
func (p *pool) loan() (iPoolObject, error) {
p.mulock.Lock()
defer p.mulock.Unlock()
if len(p.idle) == 0 {
return nil, fmt.Errorf("No pool object free. Please request after sometime")
}
obj := p.idle[0]
p.idle = p.idle[1:]
p.active = append(p.active, obj)
p.inUse = p.inUse + 1
fmt.Printf("Loan Pool Object with ID: %s\n", obj.getID())
return obj, nil
}
func (p *pool) receive(target iPoolObject) error {
p.mulock.Lock()
defer p.mulock.Unlock()
err := p.remove(target)
if err != nil {
return err
}
p.idle = append(p.idle, target)
p.inUse = p.inUse - 1
fmt.Printf("Return Pool Object with ID: %s\n", target.getID())
return nil
}
func (p *pool) remove(target iPoolObject) error {
currentActiveLength := len(p.active)
for i, obj := range p.active {
if obj.getID() == target.getID() {
p.active[currentActiveLength-1], p.active[i] = p.active[i], p.active[currentActiveLength-1]
p.active = p.active[:currentActiveLength-1]
return nil
}
}
return fmt.Errorf("Targe pool object doesn't belong to the pool")
}
func (p *pool) setMaxCapacity(capacity int) {
p.capacity = capacity
}
type iPoolObject interface {
getID() string //This is any id which can be used to compare two different pool objects
doSomething()
}
type dbconnection struct {
id string
}
func (c *dbconnection) getID() string {
return c.id
}
func (c *dbconnection) doSomething() {
fmt.Printf("Connection with id %s in action\n", c.getID())
}
type client struct {
pool *pool
}
func (c *client) init() {
connections := make([]iPoolObject, 0)
for i := 0; i < 3; i++ {
c := &dbconnection{id: strconv.Itoa(i)}
connections = append(connections, c)
}
var err error
c.pool, err = initPool(connections)
if err != nil {
log.Fatalf("Init Pool Error: %s", err)
}
}
func (c *client) doWork() {
fmt.Printf("Capacity: %d\n\n", c.pool.capacity)
conn1, err := c.pool.loan()
if err != nil {
log.Fatalf("Pool Loan Error: %s", err)
}
conn1.doSomething()
fmt.Printf("InUse: %d\n\n", c.pool.inUse)
conn2, err := c.pool.loan()
if err != nil {
log.Fatalf("Pool Loan Error: %s", err)
}
conn2.doSomething()
fmt.Printf("InUse: %d\n\n", c.pool.inUse)
c.pool.receive(conn1)
fmt.Printf("InUse: %d\n\n", c.pool.inUse)
c.pool.receive(conn2)
fmt.Printf("InUse: %d\n", c.pool.inUse)
}
func main() {
client := &client{}
client.init()
client.doWork()
}
Output
Capacity: 3
Loan Pool Object with ID: 0
Connection with id 0 in action
InUse: 1
Loan Pool Object with ID: 1
Connection with id 1 in action
InUse: 2
Return Pool Object with ID: 0
InUse: 1
Return Pool Object with ID: 1
InUse: 0
Conclusion
This is all about designing an Object Pool. Hope you have liked this article. Please share feedback in the comments