Заместитель (англ. Proxy) — структурный шаблон проектирования, предоставляет объект, который контролирует
доступ к другому объекту, перехватывая все вызовы (выполняет функцию контейнера).
Наиболее частым применением шаблона Прокси является ленивая загрузка (lazy load). “Тяжелые” объекты не всегда разумно загружать в момент инициализации. Более правильным решением будет загрузить его по первому требованию.
Представим нашу кухню(Kitchen
).
У нас есть какой-то склад булок(Kitchen->muffinPool
) для бургеров. Заказы идут - булки расходуются.
У нас есть контакты проверенного поставщика(MuffinForwarder
), который снабжает нас булками. У поставщика так же есть
какое-то хранилище, и связь с фабрикой(MuffinFactory
) которая производи булки.
В нашем примере Заместителей будет - два. Это кухня и поставщик.
Рассмотрим всю суть чуть ближе.
- Фабрика(
MuffinFactory
) - просто производит булки. Вам нужна булка?MuffinFactory->getProduct()
отдаст вам новый объект типа Product. - Поставщик(
MuffinForwarder
) - имеет какое-то хранилище для булок.MuffinForwarder->getProduct()
отдаст вам объект типаProduct
из своего хранилища. Как только булки заканчиваются, он обращается к Фабрике и делает закупку. - Кухня(
Kitchen
) - как и поставщик имеет хранилище.Kitchen->getProduct()
так же отдаст вам объект типаProduct
из своего хранилища. Как только есть необходимость в булках мы обращаемся к поставщику.
А теперь давайте поймем зачем нам всё это нужно? Если у поставщика есть булки, время их доставки на кухню занимает какое-то время. Например 10 минут. Если у поставщика их нет, он заказывает их у фабрики. Производство одной занимает например 1 минуту. Время это ресурс который потребляет наши объекты. И инициализация их нужна только по мере необходимости. Данный шаблон очень похож на Декоратор. Что бы уловить суть представим что клиенту недоступна ни фабрика, ни поставщик. Он просто-напросто не сможет получить продукт. Он знает что есть кухня и всё.
-
Factory
-
Product
-
Product.php
<?php namespace DesignPatterns\Structural\Proxy\Factory\Product; /** * Class Product * @package DesignPatterns\Structural\Proxy\Factory\Product */ class Product { public function __construct() { } }
-
FactoryInterface.php
<?php namespace DesignPatterns\Structural\Proxy\Factory; use DesignPatterns\Structural\Proxy\Factory\Product\Product; /** * Interface FactoryInterface * @package DesignPatterns\Structural\Proxy\Factory */ interface FactoryInterface { /** * @return Product */ public function getProduct(): Product; }
-
MuffinFactory.php
<?php namespace DesignPatterns\Structural\Proxy\Factory; use DesignPatterns\Structural\Proxy\Factory\Product\Product; /** * Class MuffinFactory * @package DesignPatterns\Structural\Proxy\Factory */ class MuffinFactory implements FactoryInterface { /** * @return Product */ public function getProduct(): Product { return new Product(); } }
-
-
Forwarder
-
MuffinForwarder.php
<?php namespace DesignPatterns\Structural\Proxy\Forwarder; use DesignPatterns\Structural\Proxy\AbstractPurchase; use DesignPatterns\Structural\Proxy\Factory\FactoryInterface; use DesignPatterns\Structural\Proxy\Factory\MuffinFactory; use DesignPatterns\Structural\Proxy\Factory\Product\Product; /** * Class MuffinForwarder * @package DesignPatterns\Structural\Proxy\Forwarder */ class MuffinForwarder extends AbstractPurchase { const PARTY_SIZE = 20; /** * @var FactoryInterface */ protected $factory = null; /** * @var Product[] */ public $muffinPool = []; /** * @return FactoryInterface */ protected function getFactory(): FactoryInterface { if (is_null($this->factory)) { $this->factory = new MuffinFactory(); } return $this->factory; } /** * @return Product */ public function getProduct(): Product { if (!count($this->muffinPool)) { $this->purchaseProducts(); } return array_pop($this->muffinPool); } /** * @return void */ public function purchaseProducts() { for ($i = 0; $i < self::PARTY_SIZE; $i++) { array_push( $this->muffinPool, $this->getFactory()->getProduct() ); } $this->incrementCountPurchase(); } }
-
-
Kitchen
-
Kitchen.php
<?php namespace DesignPatterns\Structural\Proxy\Kitchen; use DesignPatterns\Structural\Proxy\AbstractPurchase; use DesignPatterns\Structural\Proxy\Factory\Product\Product; use DesignPatterns\Structural\Proxy\Forwarder\MuffinForwarder; /** * Class Kitchen * @package DesignPatterns\Structural\Proxy\Kitchen */ class Kitchen extends AbstractPurchase { const PARTY_SIZE = 5; /** * @var Product[] */ public $muffinPool = []; /** * @var MuffinForwarder */ protected $forwarder = null; /** * @return Product */ public function getProduct(): Product { if (!count($this->muffinPool)) { $this->purchaseProducts(); } return array_pop($this->muffinPool); } /** * @return MuffinForwarder */ public function getForwarder(): MuffinForwarder { if (is_null($this->forwarder)) { $this->forwarder = new MuffinForwarder(); } return $this->forwarder; } /** * @return void */ public function purchaseProducts() { for ($i = 0; $i < self::PARTY_SIZE; $i++) { array_push( $this->muffinPool, $this->getForwarder()->getProduct() ); } $this->incrementCountPurchase(); } }
-
-
AbstractPurchase.php
<?php namespace DesignPatterns\Structural\Proxy; /** * Class AbstractPurchase * @package DesignPatterns\Structural\Proxy */ abstract class AbstractPurchase { public $muffinPurchaseCount = 0; /** * @return void */ abstract public function purchaseProducts(); /** * @return void */ protected function incrementCountPurchase() { $this->muffinPurchaseCount++; } /** * @return int */ public function getPartySize(): int { return static::PARTY_SIZE; } }
Test case:
ProxyTest.php
<?php
namespace DesignPatterns\Tests\Structural\Proxy;
use DesignPatterns\Structural\Proxy\Forwarder\MuffinForwarder;
use DesignPatterns\Structural\Proxy\Kitchen\Kitchen;
class ProxyTest extends \PHPUnit_Framework_TestCase
{
/**
* @var Kitchen
*/
protected static $kitchen;
public static function setUpBeforeClass(): void
{
self::$kitchen = new Kitchen();
}
public function testKitchenMuffinPoolEmpty()
{
$this->assertEquals(0, count(self::$kitchen->muffinPool));
}
public function testKitchenNotPurchaseMuffins()
{
$this->assertEquals(0, self::$kitchen->muffinPurchaseCount);
}
public function testKitchenGetForwarder()
{
$forwarder = self::$kitchen->getForwarder();
$this->assertInstanceOf('DesignPatterns\Structural\Proxy\Forwarder\MuffinForwarder', $forwarder);
$this->assertSame(self::$kitchen->getForwarder(), $forwarder);
return $forwarder;
}
/**
* @depends testKitchenGetForwarder
*
* @param MuffinForwarder $forwarder
*/
public function testForwarderNotPurchaseMuffins(MuffinForwarder $forwarder)
{
$this->assertEquals(0, $forwarder->muffinPurchaseCount);
}
/**
* @depends testKitchenGetForwarder
*
* @param MuffinForwarder $forwarder
*/
public function testPurchase(MuffinForwarder $forwarder)
{
self::$kitchen->purchaseProducts();
$this->assertEquals(self::$kitchen->getPartySize(), count(self::$kitchen->muffinPool));
$this->assertEquals(1, self::$kitchen->muffinPurchaseCount);
$this->assertEquals(1, $forwarder->muffinPurchaseCount);
}
public function testKitchenGetProduct()
{
$product = self::$kitchen->getProduct();
$this->assertInstanceOf('DesignPatterns\Structural\Proxy\Factory\Product\Product', $product);
$this->assertNotEquals(self::$kitchen->getPartySize(), count(self::$kitchen->muffinPool));
$this->assertEquals(self::$kitchen->getPartySize() - 1, count(self::$kitchen->muffinPool));
}
}