facade Фасад (англ. Facade) належить до класу структурних шаблонів. Являє собою уніфікований інтерфейс замість набору інтерфейсів деякої підсистеми.

Фасад призначений для поділу клієнта та підсистеми. Фасад не забороняє прямий доступ до підсистеми. Він робить його простішим і зрозумілішим.

Сам шаблон дуже схожий на Будівник. Не варто забувати, що Будівельник породжує, а Фасад - структурний. Коли наш клієнт робить замовлення, він не хоче знати, що, де, коли відбувається. Він просто робить замовлення у касира (** CashMan**). А касир уже знає, що робити з цими даними, і з якими частинами системи взаємодіяти задля досягнення результату.

  • BurgerWorker.php
                  
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    /**
     * Class BurgerWorker
     * @package DesignPatterns\Structural\Facade
     */
    class BurgerWorker
    {
        /**
         * @var array
         */
        protected $burgers = [
            'hamburger'    => [
                'muffin'     => 'muffin',
                'meat'       => 'beef',
                'sauce'      => 'ketchup',
                'withCheese' => false,
            ],
            'cheeseburger' => [
                'muffin'     => 'muffin',
                'meat'       => 'chicken',
                'sauce'      => 'mayonnaise',
                'withCheese' => true,
            ],
        ];
    
        /**
         * @var array
         */
        protected $prices = [
            'hamburger'    => 12.5,
            'cheeseburger' => 14.25,
        ];
    
        /**
         * @param string $burgerName
         *
         * @return array
         */
        public function make(string $burgerName): array
        {
            return $this->burgers[$burgerName];
        }
    
        /**
         * @param string $burgerName
         *
         * @return float
         */
        public function getPrice(string $burgerName): float
        {
            return $this->prices[$burgerName];
        }
    }
    
                  
  • CashBox.php
                  
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    use Exception;
    
    /**
     * Class CashBox
     * @package DesignPatterns\Structural\Facade
     */
    class CashBox
    {
        /**
         * @var float
         */
        protected $cash;
    
        /**
         * CashBox constructor.
         *
         * @param float $cash
         */
        public function __construct(float $cash)
        {
            $this->cash = $cash;
        }
    
        /**
         * @return float
         */
        public function getCash(): float
        {
            return $this->cash;
        }
    
        /**
         * @param $orderPrice
         * @param $cash
         *
         * @return float
         * @throws Exception
         */
        public function makeOrder($orderPrice, $cash): float
        {
            $change = $cash - $orderPrice;
    
            if ($change < 0) {
                throw new Exception('Need more money.');
            }
    
            $this->cash += $cash - $change;
    
            return $change;
        }
    }
    
                  
  • CashMan.php
                  
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    use InvalidArgumentException;
    
    /**
     * Class CashMan
     * @package DesignPatterns\Structural\Facade
     */
    class CashMan
    {
        /**
         * @var BurgerWorker
         */
        protected $burgerWorker;
    
        /**
         * @var WaterWorker
         */
        protected $waterWorker;
    
        /**
         * @var CashBox
         */
        protected $cashBox;
    
        /**
         * @var array
         */
        protected $order = [];
    
        /**
         * @var float
         */
        protected $orderPrice = 0.0;
    
        /**
         * CashMan constructor.
         *
         * @param BurgerWorker $burgerWorker
         * @param WaterWorker $waterWorker
         * @param CashBox $cashBox
         */
        public function __construct(BurgerWorker $burgerWorker, WaterWorker $waterWorker, CashBox $cashBox)
        {
            $this->burgerWorker = $burgerWorker;
            $this->waterWorker = $waterWorker;
            $this->cashBox = $cashBox;
        }
    
        /**
         * @param array $order
         * @param $cash
         *
         * @return array
         */
        public function makeOrder(array $order, $cash)
        {
            if (!array_key_exists('waters', $order) && !array_key_exists('burgers', $order)) {
                throw new InvalidArgumentException();
            }
    
            if (array_key_exists('waters', $order)) {
                $this->setProductsToOrderWithPrice($this->waterWorker, $order['waters']);
            }
    
            if (array_key_exists('burgers', $order)) {
                $this->setProductsToOrderWithPrice($this->burgerWorker, $order['burgers']);
            }
    
            $change = $this->cashBox->makeOrder($this->orderPrice, $cash);
    
            $fullOrder = [
                'order'  => $this->order,
                'price'  => $this->orderPrice,
                'change' => $change,
            ];
    
            $this->resetResult();
    
            return $fullOrder;
        }
    
        /**
         * @param WaterWorker|BurgerWorker $worker
         * @param array|string $products
         *
         * @return void
         */
        protected function setProductsToOrderWithPrice($worker, $products)
        {
            if (is_array($products)) {
                foreach ($products as $product) {
                    $this->setProductAndPrice($worker, $product);
                }
            } else {
                $this->setProductAndPrice($worker, $products);
            }
        }
    
        /**
         * @param WaterWorker|BurgerWorker $worker
         * @param string $product
         *
         * @return void
         */
        protected function setProductAndPrice($worker, $product)
        {
            $this->order[] = $worker->make($product);
            $this->orderPrice += $worker->getPrice($product);
        }
    
        /**
         * @return void
         */
        protected function resetResult()
        {
            $this->orderPrice = 0;
            $this->order = [];
        }
    }
    
                  
  • WaterWorker.php
                  
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    /**
     * Class WaterWorker
     * @package DesignPatterns\Structural\Facade
     */
    class WaterWorker
    {
        /**
         * @var array
         */
        protected $waters = [
            'cocaCola' => 'Coca-Cola',
            'fanta'    => 'Fanta',
            'sprite'   => 'Sprite',
        ];
    
        /**
         * @var array
         */
        protected $prices = [
            'cocaCola' => 2,
            'fanta'    => 3,
            'sprite'   => 1,
        ];
    
        /**
         * @param string $waterName
         *
         * @return string
         */
        public function make(string $waterName): string
        {
            return $this->waters[$waterName];
        }
    
        /**
         * @param string $waterName
         *
         * @return float
         */
        public function getPrice(string $waterName): float
        {
            return $this->prices[$waterName];
        }
    }
    
                  
  • Test case:

    FacadeTest.php
      
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    class FacadeTest extends \PHPUnit_Framework_TestCase
    {
        const MONEY_IN_CASH_BOX = 1000;
    
        protected $firstOrder = [
            'burgers' => 'hamburger',
            'waters'  => 'fanta',
            'cash'    => 30,
        ];
    
        protected $secondOrder = [
            'burgers' => [
                'hamburger',
                'cheeseburger',
            ],
            'waters'  => 'cocaCola',
            'cash'    => 50,
        ];
    
        /**
         * @var CashBox
         */
        protected $cashBox;
    
        /**
         * @var CashMan
         */
        protected $cashMan;
    
        /**
         * @var BurgerWorker
         */
        protected $burgerWorker;
    
        /**
         * @var WaterWorker
         */
        protected $waterWorker;
    
        public function setUp(): void
        {
            $this->cashBox = new CashBox(self::MONEY_IN_CASH_BOX);
            $this->burgerWorker = new BurgerWorker();
            $this->waterWorker = new WaterWorker();
    
            $this->cashMan = new CashMan(
                $this->burgerWorker,
                $this->waterWorker,
                $this->cashBox
            );
        }
    
        public function testMakeFirstOrder()
        {
            $this->assertEquals($this->cashBox->getCash(), self::MONEY_IN_CASH_BOX);
    
            $firstOrder = $this->cashMan->makeOrder($this->firstOrder, $this->firstOrder['cash']);
    
            $this->assertIsArray($firstOrder);
            $this->assertArrayHasKey('price', $firstOrder);
            $this->assertArrayHasKey('change', $firstOrder);
    
            $this->assertEquals($firstOrder['change'], $this->firstOrder['cash'] - $firstOrder['price']);
    
            $this->assertEquals($this->cashBox->getCash(), self::MONEY_IN_CASH_BOX + $firstOrder['price']);
    
            return $firstOrder;
        }
    
        public function testMakeSecondOrder()
        {
            $secondOrder = $this->cashMan->makeOrder($this->secondOrder, $this->secondOrder['cash']);
    
            $this->assertIsArray($secondOrder);
            $this->assertArrayHasKey('price', $secondOrder);
            $this->assertArrayHasKey('change', $secondOrder);
    
            $this->assertEquals($secondOrder['change'], $this->secondOrder['cash'] - $secondOrder['price']);
    
            $this->assertEquals(
                $this->cashBox->getCash(),
                self::MONEY_IN_CASH_BOX + $secondOrder['price']
            );
        }
    }