Фабричный метод (англ. 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);
}
}