factory-method Фабричный метод (англ. Factory Method также известен как Виртуальный конструктор (англ. Virtual Constructor)) — порождающий шаблон проектирования, предоставляющий подклассам интерфейс для создания экземпляров некоторого класса. В момент создания наследники могут определить, какой класс создавать. Иными словами, Фабрика делегирует создание объектов наследникам родительского класса. Это позволяет использовать в коде программы, не специфические классы, а манипулировать абстрактными объектами на более высоком уровне.

Класс Burger, абстрактный класс. Глядя на него мы можем понять, что должен собой представлять бургер. И что все бургеры должны быть похожи на него. Создадим два наследуемых класса от Burger с некоторыми особенностями. Получили Hamburger и Cheeseburger. На нашей кухне есть повар(new Chef()) реализует интерфейс FactoryMethodInterface, значит, он умеет делать бургеры. Каждый повар может создавать бургеры по-разному, но на выходе мы всегда получим Burger. Что бы повар приготовил нам бургер мы вызываем его метод (Chef::makeBurger()) и передаём название бургера. Повар создает нужный нам бургер или говорит что такой бургер он готовить не умеет.

  • Burgers
    • Burger.php
                    
      <?php
      
      namespace DesignPatterns\Creational\FactoryMethod\Burgers;
      
      /**
       * Class Burger
       * @package DesignPatterns\Creational\FactoryMethod\Burgers
       */
      abstract class Burger
      {
          /**
           * @var string
           */
          protected $meat;
      
          /**
           * @var string
           */
          protected $sauce;
      
          /**
           * @var bool
           */
          protected $withCheese;
      
          /**
           * @return string
           */
          public function getMeat(): string
          {
              return $this->meat;
          }
      
          /**
           * @param string $meat
           *
           * @return void
           */
          public function setMeat(string $meat)
          {
              $this->meat = $meat;
          }
      
          /**
           * @return string
           */
          public function getSauce(): string
          {
              return $this->sauce;
          }
      
          /**
           * @param string $sauce
           *
           * @return void
           */
          public function setSauce(string $sauce)
          {
              $this->sauce = $sauce;
          }
      
          /**
           * @return bool
           */
          public function getWithCheese(): bool
          {
              return $this->withCheese;
          }
      
          /**
           * @param bool $withCheese
           *
           * @return void
           */
          public function setWithCheese(bool $withCheese)
          {
              $this->withCheese = $withCheese;
          }
      }
      
                    
    • Cheeseburger.php
                    
      <?php
      
      namespace DesignPatterns\Creational\FactoryMethod\Burgers;
      
      /**
       * Class Cheeseburger
       * @package DesignPatterns\Creational\FactoryMethod\Burgers
       */
      class Cheeseburger extends Burger
      {
          /**
           * @var string
           */
          protected $meat = 'chicken';
      
          /**
           * @var string
           */
          protected $sauce = 'mayonnaise';
      
          /**
           * @var bool
           */
          protected $withCheese = true;
      }
      
                    
    • Hamburger.php
                    
      <?php
      
      namespace DesignPatterns\Creational\FactoryMethod\Burgers;
      
      /**
       * Class Hamburger
       * @package DesignPatterns\Creational\FactoryMethod\Burgers
       */
      class Hamburger extends Burger
      {
          /**
           * @var string
           */
          protected $meat = 'beef';
      
          /**
           * @var string
           */
          protected $sauce = 'ketchup';
      
          /**
           * @var bool
           */
          protected $withCheese = false;
      }
      
                    
  • Chef.php
                  
    <?php
    
    namespace DesignPatterns\Creational\FactoryMethod;
    
    use DesignPatterns\Creational\FactoryMethod\Burgers\Burger;
    use DesignPatterns\Creational\FactoryMethod\Burgers\Hamburger;
    use DesignPatterns\Creational\FactoryMethod\Burgers\Cheeseburger;
    
    /**
     * Class Chef
     * @package DesignPatterns\Creational\FactoryMethod
     */
    class Chef implements FactoryMethodInterface
    {
        /**
         * @param string $typeBurger
         *
         * @return Burger
         */
        public function makeBurger(string $typeBurger): Burger
        {
            $typeBurger = strtolower($typeBurger);
    
            switch ($typeBurger) {
                case 'cheeseburger':
                    return new Cheeseburger();
                case 'hamburger':
                    return new Hamburger();
                default:
                    throw new \InvalidArgumentException('Sorry. But we haven\'t this burger.');
            }
        }
    }
    
                  
  • FactoryMethodInterface.php
                  
    <?php
    
    namespace DesignPatterns\Creational\FactoryMethod;
    
    use DesignPatterns\Creational\FactoryMethod\Burgers\Burger;
    
    /**
     * Interface FactoryMethodInterface
     * @package DesignPatterns\Creational\FactoryMethod
     */
    interface FactoryMethodInterface
    {
        /**
         * @param string $burgerType
         *
         * @return Burger
         */
        public function makeBurger(string $burgerType): Burger;
    }
    
                  
  • Test case:

    FactoryMethodTest.php
      
    <?php
    
    namespace DesignPatterns\Tests\Creational\FactoryMethod;
    
    use DesignPatterns\Creational\FactoryMethod\Chef;
    use InvalidArgumentException;
    use PHPUnit_Framework_TestCase;
    
    class FactoryMethodTest extends PHPUnit_Framework_TestCase
    {
        public function testWorkerInstanceOfFactoryMethodInterface()
        {
            $chef = new Chef();
    
            $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\FactoryMethodInterface', $chef);
        }
    
        public function testHamburgerInstanceOfBurger()
        {
            $chef = new Chef();
    
            $hamburger = $chef->makeBurger('hamburger');
            $someHamburger = $chef->makeBurger('hamburger');
    
            $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\Burgers\Burger', $hamburger);
            $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\Burgers\Burger', $someHamburger);
            $this->assertNotSame($hamburger, $someHamburger);
        }
    
        public function testCheeseburgerInstanceOfBurger()
        {
            $chef = new Chef();
    
            $cheeseburger = $chef->makeBurger('cheeseburger');
            $someCheeseburger = $chef->makeBurger('cheeseburger');
    
            $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\Burgers\Burger', $cheeseburger);
            $this->assertInstanceOf('DesignPatterns\Creational\FactoryMethod\Burgers\Burger', $someCheeseburger);
            $this->assertNotSame($cheeseburger, $someCheeseburger);
        }
    
        public function testException()
        {
            $this->expectException(InvalidArgumentException::class);
            $chef = new Chef();
    
            $chef->makeBurger('someNameForBurger');
        }
    
        public function testHamburgerСomposition()
        {
            $chef = new Chef();
    
            $hamburger = $chef->makeBurger('hamburger');
    
            $hamburgerMeat = $hamburger->getMeat();
            $hamburgerSauce = $hamburger->getSauce();
            $hamburgerWithCheese = $hamburger->getWithCheese();
    
            $this->assertEquals('beef', $hamburgerMeat);
            $this->assertNotEquals('chicken', $hamburgerMeat);
    
            $this->assertEquals('ketchup', $hamburgerSauce);
            $this->assertNotEquals('mayonnaise', $hamburgerSauce);
    
            $this->assertEquals(false, $hamburgerWithCheese);
            $this->assertNotEquals(true, $hamburgerWithCheese);
        }
    
        public function testCheeseburgerСomposition()
        {
            $chef = new Chef();
    
            $cheeseburger = $chef->makeBurger('cheeseburger');
    
            $cheeseburgerMeat = $cheeseburger->getMeat();
            $cheeseburgerSauce = $cheeseburger->getSauce();
            $cheeseburgerWithCheese = $cheeseburger->getWithCheese();
    
            $this->assertEquals('chicken', $cheeseburgerMeat);
            $this->assertNotEquals('beef', $cheeseburgerMeat);
    
            $this->assertEquals('mayonnaise', $cheeseburgerSauce);
            $this->assertNotEquals('ketchup', $cheeseburgerSauce);
    
            $this->assertEquals(true, $cheeseburgerWithCheese);
            $this->assertNotEquals(false, $cheeseburgerWithCheese);
        }
    }