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