Замісник (англ. 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));
}
}