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