singleton Одинак (англ. Singleton) — шаблон проєктування, що породжує, дозволяє містити лише один екземпляр об’єкта в додатку, який оброблятиме всі звернення, забороняючи створювати новий екземпляр. Один із найвідоміших і, мабуть, найсуперечливіших шаблонів.

Застосування: Для деяких класів важливо, щоб існував лише один екземпляр.

  • У системі може бути багато принтерів, але можливий лише один спулер.
  • Повинні бути лише одна файлова система та єдиний віконний менеджер.
  • У цифровому фільтрі може бути лише один аналого-цифровий перетворювач (АЦП). Бухгалтерська система обслуговує лише одну компанію.
  • Одне підключення до БД.

Ми маємо невелику закусочну, в якій є один касовий апарат. Для звітності до податкової використовується чекова стрічка, на якій зберігатимуться всі фінансові операції. Для реалізації касового апарату ми використовуємо шаблон Singleton. Принцип Singleton простий, як п’ять копійок. Для того, щоб забезпечити існування тільки одного екземпляра класу Cashbox, ми закрили всі магічні методи для створення екземпляра класу, клонування та серіалізації. Єдиний можливий спосіб отримати об’єкт – скористатися статичним методом Cashbox::getInstance().

Примітка: Незважаючи на зручність застосування даного шаблону, він є одним із найсуперечливіших при розробці, тож рекомендується його застосовувати, тільки якщо немає жодного іншого рішення, оскільки він створює значні складнощі під час тестування коду. Є такий антипаттерн, Самотність (Singletonitis), який саме полягає у недоречному використанні синглтонів.

  • Cashbox.php
                  
    <?php
    
    namespace DesignPatterns\Creational\Singleton;
    
    /**
     * Class Сashbox
     * @package DesignPatterns\Creational\Singleton
     */
    class Cashbox implements SingletonInterface
    {
        use SingletonTrait, CashboxTrait;
    }
    
                  
  • CashboxTrait.php
                  
    <?php
    
    namespace DesignPatterns\Creational\Singleton;
    
    trait CashboxTrait
    {
        /**
         * @var array
         */
        private $cash = [];
    
        /**
         * Return sum for all cash operation on cashbox
         *
         * @return float
         */
        public function getAllCash(): float
        {
            return array_sum($this->cash);
        }
    
        /**
         * Set new cash operation
         *
         * @param float $cash
         *
         * @return void
         */
        public function setCash(float $cash)
        {
            $this->cash[uniqid()] = $cash;
        }
    }
    
                  
  • SingletonInterface.php
                  
    <?php
    
    namespace DesignPatterns\Creational\Singleton;
    
    /**
     * Interface SingletonInterface
     * @package DesignPatterns\Creational\Singleton
     */
    interface SingletonInterface
    {
        /**
         * @return SingletonInterface
         */
        public static function getInstance(): SingletonInterface;
    }
    
                  
  • SingletonTrait.php
                  
    <?php
    
    namespace DesignPatterns\Creational\Singleton;
    
    /**
     * Trait SingletonTrait
     * @package DesignPatterns\Creational\Singleton
     */
    trait SingletonTrait
    {
        /**
         * @var SingletonInterface
         */
        protected static $instance;
    
        /**
         * @return SingletonInterface
         */
        final public static function getInstance(): SingletonInterface
        {
            if (!static::$instance) {
                static::$instance = new static();
            }
    
            return static::$instance;
        }
    
        private function __construct()
        {
        }
    
        private function __clone()
        {
        }
    
        private function __wakeup()
        {
        }
    }
    
                  
  • Test case:

    SingletonTest.php
      
    <?php
    
    namespace DesignPatterns\Tests\Creational\Singleton;
    
    use DesignPatterns\Creational\Singleton\Cashbox;
    use PHPUnit_Framework_TestCase;
    
    /**
     * Class SingletonTest
     * @package DesignPatterns\Tests\Creational\Singleton
     */
    class SingletonTest extends PHPUnit_Framework_TestCase
    {
        /**
         * @var Cashbox
         */
        protected $shopCashbox;
    
        protected function setUp(): void
        {
            $this->shopCashbox = Cashbox::getInstance();
        }
    
        public function testInstanceOfSingleton()
        {
            $this->assertInstanceOf('DesignPatterns\Creational\Singleton\Cashbox', $this->shopCashbox);
        }
    
        public function testUniquePropertyFirstPurchase(): int
        {
            $sumPurchases = 2;
            $this->shopCashbox->setCash($sumPurchases);
    
            $this->assertEquals($sumPurchases, $this->shopCashbox->getAllCash());
    
            return $sumPurchases;
        }
    
        /**
         * @depends testUniquePropertyFirstPurchase
         *
         * @param int $sumPurchases
         *
         * @return int
         */
        public function testUniquePropertySecondPurchase(int $sumPurchases): int
        {
            $secondPurchase = 4;
            $this->shopCashbox->setCash($secondPurchase);
            $sumPurchases += $secondPurchase;
    
            $this->assertEquals($sumPurchases, $this->shopCashbox->getAllCash());
    
            return $sumPurchases;
        }
    
        /**
         * @depends testUniquePropertySecondPurchase
         *
         * @param int $sumPurchases
         */
        public function testUniquePropertySellerHaveMistake(int $sumPurchases)
        {
            $sellerMistake = -3;
            $this->shopCashbox->setCash($sellerMistake);
            $sumPurchases += $sellerMistake;
    
            $this->assertEquals($sumPurchases, $this->shopCashbox->getAllCash());
        }
    }