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());
        }
    }