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);
        }
    }