pool Пул Об’єктів (англ. Object pool) - це хеш, в який можна складати ініціалізовані об’єкти та діставати їх звідти за потреби. По суті, є окремим випадком реєстру.

Зберігання об’єктів у пулі може помітно підвищити продуктивність, коли вартість ініціалізації екземпляра класу висока, швидкість екземпляра класу висока, а кількість екземплярів, що одночасно використовуються, в будь-який момент часу є низькою. Час вилучення об’єкта з пулу легко прогнозується, на відміну створення нових об’єктів (особливо з мережевим зверху), що займає невизначений час.

Застосування:

  • Інформація про видимі об’єкти у багатьох комп’ютерних іграх. Ця інформація є актуальною лише протягом одного кадру;
  • З’єднання з базами даних;
  • З’єднання сокетів;
  • Ініціалізація великих графічних об’єктів, таких як шрифти або растрові зображення.

У нашій закусочній є відділ кадрів. Так само є один різноробочий, і він вільний. З’явилося якесь завдання, яке потрібно виконати. Ми говоримо відділу кадрів: “Дайте нам робітника”. Відділ кадрів дивиться, чи є вільні робітники. Якщо є, віддає нам одного робітника і позначає його як зайнятого.

Примітка:

  • Після того, як об’єкт повернуто, він повинен повернутися в стан, придатний для подальшого використання. Якщо об’єкти після повернення в пул опиняються в неправильному або невизначеному стані, така конструкція називається Об’єктною Клоакою (англ. Object Cesspool).
  • Повторне використання об’єктів також може призвести до витоку інформації. Якщо в об’єкті є секретні дані (наприклад, номер кредитної картки), після звільнення об’єкта цю інформацію треба затерти.
  • ShopStaff.php
                  
    <?php
    
    namespace DesignPatterns\Creational\Pool;
    
    use Countable;
    
    /**
     * Class ShopStaff
     * @package DesignPatterns\Creational\Pool
     */
    class ShopStaff implements Countable
    {
        /**
         * @var Worker[]
         */
        private $occupiedWorkers = [];
    
        /**
         * @var Worker[]
         */
        private $freeWorkers = [];
    
        /**
         * @param bool $delay
         *
         * @return Worker
         */
        public function getWorker(bool $delay = null): Worker
        {
            $workerNumber = $this->count() + 1;
    
            if (!$this->getCountFreeWorkers()) {
                $worker = new Worker($workerNumber, $delay);
            } else {
                $worker = array_pop($this->freeWorkers);
            }
    
            $this->occupiedWorkers[$this->getHash($worker)] = $worker;
    
            return $worker;
        }
    
        /**
         * @param Worker $worker
         *
         * @return void
         */
        public function dispose(Worker $worker)
        {
            $key = $this->getHash($worker);
    
            if (isset($this->occupiedWorkers[$key])) {
                unset($this->occupiedWorkers[$key]);
                $this->freeWorkers[$key] = $worker;
            }
        }
    
        /**
         * @return int
         */
        public function count(): int
        {
            return $this->getCountOccupiedWorkers() + $this->getCountFreeWorkers();
        }
    
        /**
         * @return int
         */
        public function getCountFreeWorkers(): int
        {
            return count($this->freeWorkers);
        }
    
        /**
         * @return int
         */
        public function getCountOccupiedWorkers(): int
        {
            return count($this->occupiedWorkers);
        }
    
        /**
         * @param Worker $worker
         *
         * @return string
         */
        private function getHash(Worker $worker): string
        {
            return spl_object_hash($worker);
        }
    }
    
                  
  • Worker.php
                  
    <?php
    
    namespace DesignPatterns\Creational\Pool;
    
    /**
     * Class Worker
     * @package DesignPatterns\Creational\Pool
     */
    class Worker implements WorkerInterface
    {
        /**
         * @var int
         */
        protected $workerNumber;
    
        /**
         * Worker constructor.
         *
         * @param int $workerNumber
         * @param bool $performance
         */
        public function __construct(int $workerNumber, $performance = false)
        {
            if ($performance) {
                $this->getDelay();
            }
    
            $this->setNumberWorker($workerNumber);
        }
    
        /**
         * @return string
         */
        public function run(): string
        {
            return 'Hello. My number is ' . $this->getNumberWorker() . '!';
        }
    
        /**
         * @return int
         */
        private function getNumberWorker(): int
        {
            return $this->workerNumber;
        }
    
        /**
         * @param int $workerNumber
         *
         * @return void
         */
        private function setNumberWorker(int $workerNumber)
        {
            $this->workerNumber = $workerNumber;
        }
    
        /**
         * Method for test performance Pool pattern.
         *
         * @return void
         */
        private function getDelay()
        {
            sleep(3);
        }
    }
    
                  
  • WorkerInterface.php
                  
    <?php
    
    namespace DesignPatterns\Creational\Pool;
    
    /**
     * Interface WorkerInterface
     * @package DesignPatterns\Creational\Pool
     */
    interface WorkerInterface
    {
        /**
         * WorkerInterface constructor.
         *
         * @param int $workerNumber
         * @param bool $performance
         */
        public function __construct(int $workerNumber, $performance = false);
    
        /**
         * @return string
         */
        public function run(): string;
    }
    
                  
  • Test case:

    PoolTest.php
      
    <?php
    
    namespace DesignPatterns\Tests\Creational\Pool;
    
    use DesignPatterns\Creational\Pool\ShopStaff;
    use PHPUnit_Framework_TestCase;
    
    class PoolTest extends PHPUnit_Framework_TestCase
    {
        public function testCanShopStaffCreateNewWorkers()
        {
            $pool = new ShopStaff();
            $this->assertEquals(0, $pool->count(), 'Now pool don\'t have workers.');
    
            /**
             * Add to workers
             */
            for ($i = 0; $i < 5; $i++) {
                $pool->getWorker();
            }
    
            $this->assertEquals(5, $pool->count(), 'Now pool have two workers.');
        }
    
        public function testWorkersNotSame()
        {
            $pool = new ShopStaff();
    
            $firstWorker = $pool->getWorker();
            $secondWorker = $pool->getWorker();
    
            $this->assertEquals(2, $pool->count(), 'Now pool have two workers.');
            $this->assertInstanceOf('DesignPatterns\Creational\Pool\Worker', $firstWorker);
            $this->assertInstanceOf('DesignPatterns\Creational\Pool\Worker', $secondWorker);
            $this->assertNotSame($firstWorker, $secondWorker);
        }
    
        public function testGetWorkerNumberForFiveWorkers()
        {
            $pool = new ShopStaff();
    
            for ($i = 1; $i <= 5; $i++) {
                $worker = $pool->getWorker();
                $this->assertEquals('Hello. My number is ' . $i . '!', $worker->run(), 'Worker have right number.');
            }
        }
    
        public function testCountFreeAndOccupiedWorkers()
        {
            $pool = new ShopStaff();
    
            for ($i = 0; $i < 5; $i++) {
                $pool->getWorker();
            }
    
            $this->assertEquals(5, $pool->count(), 'Now pool have five workers.');
            $this->assertEquals(5, $pool->getCountOccupiedWorkers(), 'Now pool have five occupied workers.');
            $this->assertEquals(0, $pool->getCountFreeWorkers(), 'Now pool don\'t have free workers.');
        }
    
        public function testDisposeWorker()
        {
            $pool = new ShopStaff();
    
            $firstWorker = $pool->getWorker();
            $pool->getWorker();
            $pool->getWorker();
    
            $this->assertEquals(3, $pool->getCountOccupiedWorkers(), 'Now pool have three occupied workers.');
            $this->assertEquals(0, $pool->getCountFreeWorkers(), 'Now pool don\'t have free workers.');
    
            $pool->dispose($firstWorker);
    
            $this->assertEquals(2, $pool->getCountOccupiedWorkers(), 'Now pool have two occupied workers.');
            $this->assertEquals(1, $pool->getCountFreeWorkers(), 'Now pool have one free worker.');
        }
    
        public function testPerformancePoolPattern()
        {
            $pool = new ShopStaff();
    
            /**
             * Start initialization new workers with timers.
             */
            $start = $this->getTime();
    
            $firstWorker = $pool->getWorker(true);
            $secondWorker = $pool->getWorker(true);
            $thirdWorker = $pool->getWorker(true);
    
            $finish = $this->getTime();
    
            $timeWithInitial = $finish - $start;
    
            /**
             * Dispose all workers
             */
            $pool->dispose($firstWorker);
            $pool->dispose($secondWorker);
            $pool->dispose($thirdWorker);
    
            /**
             * Get free workers with timers.
             */
            $start = $this->getTime();
    
            $pool->getWorker(true);
            $pool->getWorker(true);
            $pool->getWorker(true);
    
            $finish = $this->getTime();
    
            $timeWitOutInitial = $finish - $start;
    
            $this->assertGreaterThan(
                $timeWitOutInitial,
                $timeWithInitial,
                'Time initialization workers longer than the time without initialization.'
            );
        }
    
        private function getTime()
        {
            return (int)microtime(true);
        }
    }