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