- Суть паттерна
Фабричный метод - это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.

- Проблема
Представьте, что вы создаёте программу управления грузовыми перевозками. Сперва вы рассчитываете перевозить товары только на автомобилях. Поэтому весь ваш код работает с объектами класса Грузовик. В какой-то момент ваша программа становится настолько известной, что морские перевозчики выстраиваются в очередь и просят добавить поддержку морской логистики в программу.
Отличные новости, правда?! Но как насчёт кода? Большая часть существующего кода жёстко привязана к классам Грузовиков. Чтобы добавить в программу классы морских Судов, понадобится перелопатить всю программу. Более того, если вы потом решите добавить в программу ещё один вид транспорта, то всю эту работу придётся повторить.
В итоге вы получите ужасающий код, наполненный условными операторами, которые выполняют то или иное действие, в зависимости от класса транспорта.
- Решение
Паттерн Фабричный метод предлагает создавать объекты не напрямую, используя оператор new, а через вызов особого фабричного метода. Не пугайтесь, объекты всё равно будут создаваться при помощи new, но делать это будет фабричный метод.
На первый взгляд, это может показаться бессмысленным: мы просто переместили вызов конструктора из одного конца программы в другой. Но теперь вы сможете переопределить фабричный метод в подклассе, чтобы изменить тип создаваемого продукта.
Чтобы эта система заработала, все возвращаемые объекты должны иметь общий интерфейс. Подклассы смогут производить объекты различных классов, следующих одному и тому же интерфейсу.
Например, классы Грузовик и Судно реализуют интерфейс Транспорт с методом доставить. Каждый из этих классов реализует метод по-своему: грузовики везут грузы по земле, а суда - по морю. Фабричный метод в классе ДорожнойЛогистики вернёт объект-грузовик, а класс МорскойЛогистики - объект-судно.
Для клиента фабричного метода нет разницы между этими объектами, так как он будет трактовать их как некий абстрактный Транспорт. Для него будет важно, чтобы объект имел метод доставить, а как конкретно он работает - не важно.
- Структура

- Шаги реализации
- Преимущества
- Недостатки
Factory Method - это порождающий паттерн проектирования, который решает проблему создания различных продуктов, без указания конкретных классов продуктов. Фабричный метод задаёт метод, который следует использовать вместо вызова оператора new для создания объектов-продуктов. Подклассы могут переопределить этот метод, чтобы изменять тип создаваемых продуктов.
Концептуальный пример
/**
* Класс Создатель объявляет фабричный метод, который должен возвращать объект
* класса Продукт. Подклассы Создателя обычно предоставляют реализацию этого метода.
*/
abstract class Creator
{
/**
* Обратите внимание, что Создатель может также обеспечить реализацию
* фабричного метода по умолчанию.
*/
abstract public function factoryMethod();
/**
* Также заметьте, что, несмотря на название, основная обязанность Создателя
* не заключается в создании продуктов. Обычно он содержит некоторую базовую
* бизнес-логику, которая основана на объектах Продуктов, возвращаемых
* фабричным методом. Подклассы могут косвенно изменять эту бизнес-логику,
* переопределяя фабричный метод и возвращая из него другой тип продукта.
*/
public function someOperation()
{
// Вызываем фабричный метод, чтобы получить объект-продукт.
$product = $this->factoryMethod();
// Далее, работаем с этим продуктом.
$result = "Creator: The same creator's code has just worked with " . $product->operation();
return $result;
}
}
/**
* Конкретные Создатели переопределяют фабричный метод для того, чтобы изменить
* тип результирующего продукта.
*/
class ConcreteCreator1 extends Creator
{
/**
* Обратите внимание, что сигнатура метода по-прежнему использует тип
* абстрактного продукта, хотя фактически из метода возвращается конкретный
* продукт. Таким образом, Создатель может оставаться независимым от
* конкретных классов продуктов.
*/
public function factoryMethod()
{
return new ConcreteProduct1();
}
}
class ConcreteCreator2 extends Creator
{
public function factoryMethod()
{
return new ConcreteProduct2();
}
}
/**
* Интерфейс Продукта объявляет операции, которые должны выполнять все
* конкретные продукты.
*/
interface Product
{
public function operation();
}
/**
* Конкретные Продукты предоставляют различные реализации интерфейса Продукта.
*/
class ConcreteProduct1 implements Product
{
public function operation()
{
return "{Result of the ConcreteProduct1}";
}
}
class ConcreteProduct2 implements Product
{
public function operation()
{
return "{Result of the ConcreteProduct2}";
}
}
/**
* Клиентский код работает с экземпляром конкретного создателя, хотя и через его
* базовый интерфейс. Пока клиент продолжает работать с создателем через базовый
* интерфейс, вы можете передать ему любой подкласс создателя.
*/
function clientCode($creator)
{
// ...
echo "Client: I'm not aware of the creator's class, but it still works.\n" . $creator->someOperation();
// ...
}
/**
* Приложение выбирает тип создателя в зависимости от конфигурации или среды.
*/
echo "App: Launched with the ConcreteCreator1.\n";
clientCode(new ConcreteCreator1());
echo "\n\n";
echo "App: Launched with the ConcreteCreator2.\n";
clientCode(new ConcreteCreator2());
Результат выполнения:
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
Пример из реальной жизни
В этом примере паттерн Factory Method предоставляет интерфейс для создания коннекторов к социальным сетям, которые могут быть использованы для входа в сеть, создания сообщений и, возможно, выполнения других действий, - и всё это без привязки клиентского кода к определённым классам конкретной социальной сети.
/**
* Создатель объявляет фабричный метод, который может быть использован вместо
* прямых вызовов конструктора продуктов, например:
*
* - До: $p = new FacebookConnector();
* - После: $p = $this->getSocialNetwork;
*
* Это позволяет подклассам SocialNetworkPoster изменять тип создаваемого продукта.
*/
abstract class SocialNetworkPoster
{
/**
* Фактический фабричный метод. Обратите внимание, что он возвращает
* абстрактный коннектор. Это позволяет подклассам возвращать любые
* конкретные коннекторы без нарушения контракта суперкласса.
*/
abstract public function getSocialNetwork();
/**
* Когда фабричный метод используется внутри бизнес-логики Создателя,
* подклассы могут изменять логику косвенно, возвращая из фабричного метода
* различные типы коннекторов.
*/
public function post($content)
{
// Вызываем фабричный метод для создания объекта Продукта...
$network = $this->getSocialNetwork();
// ...а затем используем его по своему усмотрению.
$network->logIn();
$network->createPost($content);
$network->logout();
}
}
/**
* Этот Конкретный Создатель поддерживает Facebook. Помните, что этот класс
* также наследует метод post от родительского класса. Конкретные Создатели —
* это классы, которые фактически использует Клиент.
*/
class FacebookPoster extends SocialNetworkPoster
{
private $login, $password;
public function __construct($login, $password)
{
$this->login = $login;
$this->password = $password;
}
public function getSocialNetwork()
{
return new FacebookConnector($this->login, $this->password);
}
}
/**
* Этот Конкретный Создатель поддерживает LinkedIn.
*/
class LinkedInPoster extends SocialNetworkPoster
{
private $email, $password;
public function __construct($email, $password)
{
$this->email = $email;
$this->password = $password;
}
public function getSocialNetwork()
{
return new LinkedInConnector($this->email, $this->password);
}
}
/**
* Интерфейс Продукта объявляет поведения различных типов продуктов.
*/
interface SocialNetworkConnector
{
public function logIn();
public function logOut();
public function createPost($content);
}
/**
* Этот Конкретный Продукт реализует API Facebook.
*/
class FacebookConnector implements SocialNetworkConnector
{
private $login, $password;
public function __construct($login, $password)
{
$this->login = $login;
$this->password = $password;
}
public function logIn()
{
echo "Send HTTP API request to log in user $this->login with password $this->password\n";
}
public function logOut()
{
echo "Send HTTP API request to log out user $this->login\n";
}
public function createPost($content)
{
echo "Send HTTP API requests to create a post in Facebook timeline.\n";
}
}
/**
* А этот Конкретный Продукт реализует API LinkedIn.
*/
class LinkedInConnector implements SocialNetworkConnector
{
private $email, $password;
public function __construct($email, $password)
{
$this->email = $email;
$this->password = $password;
}
public function logIn()
{
echo "Send HTTP API request to log in user $this->email with password $this->password\n";
}
public function logOut()
{
echo "Send HTTP API request to log out user $this->email\n";
}
public function createPost($content)
{
echo "Send HTTP API requests to create a post in LinkedIn timeline.\n";
}
}
/**
* Клиентский код может работать с любым подклассом SocialNetworkPoster, так как
* он не зависит от конкретных классов.
*/
function clientCode($creator)
{
// ...
$creator->post("Hello world!");
$creator->post("I had a large hamburger this morning!");
// ...
}
/**
* На этапе инициализации приложение может выбрать, с какой социальной сетью оно
* хочет работать, создать объект соответствующего подкласса и передать его клиентскому коду.
*/
echo "Testing ConcreteCreator1:\n";
clientCode(new FacebookPoster("john_smith", "******"));
echo "\n\n";
echo "Testing ConcreteCreator2:\n";
clientCode(new LinkedInPoster("john_smith@example.com", "******"));
Результат выполнения:
Testing ConcreteCreator1: Send HTTP API request to log in user john_smith with password ****** Send HTTP API requests to create a post in Facebook timeline. Send HTTP API request to log out user john_smith Send HTTP API request to log in user john_smith with password ****** Send HTTP API requests to create a post in Facebook timeline. Send HTTP API request to log out user john_smith Testing ConcreteCreator2: Send HTTP API request to log in user john_smith@example.com with password ****** Send HTTP API requests to create a post in LinkedIn timeline. Send HTTP API request to log out user john_smith@example.com Send HTTP API request to log in user john_smith@example.com with password ****** Send HTTP API requests to create a post in LinkedIn timeline. Send HTTP API request to log out user john_smith@example.com