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