1.5. 풀 (Pool)

**오브젝트 풀 패턴**은 소프트웨어 생성 패턴(Creational Patterns) 중 하나입니다. 객체들을 하나하나 할당(Allocating)하고 해제(Destroying)하기 보다는, 풀(Pool)을 이용하여 사용할 준비가 된 초기화된 객체들을 사용합니다. 풀의 사용자(Client)는 풀에서 객체를 요청하고, 반환된 객체를 이용해서 작업을 수행합니다. 그리고 사용자가 작업을 끝내면 그것을 해제(Destroy)하기 보다는 풀에 팩토리 객체의 특정 타입으로 된 객체를 반환합니다.

객체 풀링(Pooling)은 클래스를 초기화 하는 비용이 크고, 클래스의 인스턴스화 비율이 크고, 특정 시점에 사용중인 인스턴스의 숫자가 적을 때 성능향상을 제공합니다. 풀된 객체는 새로운 객체의 생성(특히 네트워크를 통해)이 다양한 시간(오래 걸리든, 짧게 걸리든)을 갖더라도, 예측가능한 시간을 얻을 수 있습니다.

그러나, 이러한 장점들은 시간상에서 비용이 큰 객체, 이를테면 데이터베이스 연결이나, 소켓 연결, 스레드나 폰트 혹은 비트맵과 같은 큰 그래픽 객체에 대해서는 사실입니다만, 특정 상황, 즉, 단순한 객체 풀링(외부 자원은 보유하지만, 전용 메모리를 차지하는)은 아마도 효과적이지 않을 수 있고 오히려 성능을 감소시킬 수 있습니다.

1.5.1. UML 다이어그램

Alt Pool UML Diagram

1.5.2. 코드

코드는 또한 GitHub 에서 볼 수 있습니다.

Pool.php

 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
<?php

namespace DesignPatterns\Creational\Pool;

class Pool
{

    private $instances = array();
    private $class;

    public function __construct($class)
    {
        $this->class = $class;
    }

    public function get()
    {
        if (count($this->instances) > 0) {
            return array_pop($this->instances);
        }

        return new $this->class();
    }

    public function dispose($instance)
    {
        $this->instances[] = $instance;
    }
}

Processor.php

 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
49
50
51
52
<?php

namespace DesignPatterns\Creational\Pool;

class Processor
{

    private $pool;
    private $processing = 0;
    private $maxProcesses = 3;
    private $waitingQueue = [];

    public function __construct(Pool $pool)
    {
        $this->pool = $pool;
    }

    public function process($image)
    {
        if ($this->processing++ < $this->maxProcesses) {
            $this->createWorker($image);
        } else {
            $this->pushToWaitingQueue($image);
        }
    }

    private function createWorker($image)
    {
        $worker = $this->pool->get();
        $worker->run($image, array($this, 'processDone'));
    }

    public function processDone($worker)
    {
        $this->processing--;
        $this->pool->dispose($worker);

        if (count($this->waitingQueue) > 0) {
            $this->createWorker($this->popFromWaitingQueue());
        }
    }

    private function pushToWaitingQueue($image)
    {
        $this->waitingQueue[] = $image;
    }

    private function popFromWaitingQueue()
    {
        return array_pop($this->waitingQueue);
    }
}

Worker.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php

namespace DesignPatterns\Creational\Pool;

class Worker
{

    public function __construct()
    {
        // let's say that constuctor does really expensive work...
        // for example creates "thread"
    }

    public function run($image, array $callback)
    {
        // do something with $image...
        // and when it's done, execute callback
        call_user_func($callback, $this);
    }
}

1.5.3. 테스트

Tests/PoolTest.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

namespace DesignPatterns\Creational\Pool\Tests;

use DesignPatterns\Creational\Pool\Pool;

class PoolTest extends \PHPUnit_Framework_TestCase
{
    public function testPool()
    {
        $pool = new Pool('DesignPatterns\Creational\Pool\Tests\TestWorker');
        $worker = $pool->get();

        $this->assertEquals(1, $worker->id);

        $worker->id = 5;
        $pool->dispose($worker);

        $this->assertEquals(5, $pool->get()->id);
        $this->assertEquals(1, $pool->get()->id);
    }
}

Tests/TestWorker.php

1
2
3
4
5
6
7
8
<?php

namespace DesignPatterns\Creational\Pool\Tests;

class TestWorker
{
    public $id = 1;
}