Пул Объектов (англ. 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);
}
}