Фабричный метод — это порождающий паттерн проектирования, который решает проблему создания различных продуктов, без указания конкретных классов продуктов.
Фабричный метод задаёт метод, который следует использовать вместо вызова оператора new
для создания объектов-продуктов. Подклассы могут переопределить этот метод, чтобы изменять тип создаваемых продуктов.
Концептуальный пример
Этот пример показывает структуру паттерна Фабричный метод , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.
index.php: Пример структуры паттерна
<?php
namespace RefactoringGuru\FactoryMethod\Conceptual;
/**
* Класс Создатель объявляет фабричный метод, который должен возвращать объект
* класса Продукт. Подклассы Создателя обычно предоставляют реализацию этого
* метода.
*/
abstract class Creator
{
/**
* Обратите внимание, что Создатель может также обеспечить реализацию
* фабричного метода по умолчанию.
*/
abstract public function factoryMethod(): Product;
/**
* Также заметьте, что, несмотря на название, основная обязанность Создателя
* не заключается в создании продуктов. Обычно он содержит некоторую базовую
* бизнес-логику, которая основана на объектах Продуктов, возвращаемых
* фабричным методом. Подклассы могут косвенно изменять эту бизнес-логику,
* переопределяя фабричный метод и возвращая из него другой тип продукта.
*/
public function someOperation(): string
{
// Вызываем фабричный метод, чтобы получить объект-продукт.
$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(): Product
{
return new ConcreteProduct1();
}
}
class ConcreteCreator2 extends Creator
{
public function factoryMethod(): Product
{
return new ConcreteProduct2();
}
}
/**
* Интерфейс Продукта объявляет операции, которые должны выполнять все
* конкретные продукты.
*/
interface Product
{
public function operation(): string;
}
/**
* Конкретные Продукты предоставляют различные реализации интерфейса Продукта.
*/
class ConcreteProduct1 implements Product
{
public function operation(): string
{
return "{Result of the ConcreteProduct1}";
}
}
class ConcreteProduct2 implements Product
{
public function operation(): string
{
return "{Result of the ConcreteProduct2}";
}
}
/**
* Клиентский код работает с экземпляром конкретного создателя, хотя и через его
* базовый интерфейс. Пока клиент продолжает работать с создателем через базовый
* интерфейс, вы можете передать ему любой подкласс создателя.
*/
function clientCode(Creator $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());
Output.txt: Результат выполнения
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}
Пример из реальной жизни
В этом примере паттерн Фабричный Метод предоставляет интерфейс для создания коннекторов к социальным сетям, которые могут быть использованы для входа в сеть, создания сообщений и, возможно, выполнения других действий, — и всё это без привязки клиентского кода к определённым классам конкретной социальной сети.
index.php: Пример из реальной жизни
<?php
namespace RefactoringGuru\FactoryMethod\RealWorld;
/**
* Создатель объявляет фабричный метод, который может быть использован вместо
* прямых вызовов конструктора продуктов, например:
*
* - До: $p = new FacebookConnector();
* - После: $p = $this->getSocialNetwork;
*
* Это позволяет подклассам SocialNetworkPoster изменять тип создаваемого
* продукта.
*/
abstract class SocialNetworkPoster
{
/**
* Фактический фабричный метод. Обратите внимание, что он возвращает
* абстрактный коннектор. Это позволяет подклассам возвращать любые
* конкретные коннекторы без нарушения контракта суперкласса.
*/
abstract public function getSocialNetwork(): SocialNetworkConnector;
/**
* Когда фабричный метод используется внутри бизнес-логики Создателя,
* подклассы могут изменять логику косвенно, возвращая из фабричного метода
* различные типы коннекторов.
*/
public function post($content): void
{
// Вызываем фабричный метод для создания объекта Продукта...
$network = $this->getSocialNetwork();
// ...а затем используем его по своему усмотрению.
$network->logIn();
$network->createPost($content);
$network->logout();
}
}
/**
* Этот Конкретный Создатель поддерживает Facebook. Помните, что этот класс
* также наследует метод post от родительского класса. Конкретные Создатели —
* это классы, которые фактически использует Клиент.
*/
class FacebookPoster extends SocialNetworkPoster
{
private $login, $password;
public function __construct(string $login, string $password)
{
$this->login = $login;
$this->password = $password;
}
public function getSocialNetwork(): SocialNetworkConnector
{
return new FacebookConnector($this->login, $this->password);
}
}
/**
* Этот Конкретный Создатель поддерживает LinkedIn.
*/
class LinkedInPoster extends SocialNetworkPoster
{
private $email, $password;
public function __construct(string $email, string $password)
{
$this->email = $email;
$this->password = $password;
}
public function getSocialNetwork(): SocialNetworkConnector
{
return new LinkedInConnector($this->email, $this->password);
}
}
/**
* Интерфейс Продукта объявляет поведения различных типов продуктов.
*/
interface SocialNetworkConnector
{
public function logIn(): void;
public function logOut(): void;
public function createPost($content): void;
}
/**
* Этот Конкретный Продукт реализует API Facebook.
*/
class FacebookConnector implements SocialNetworkConnector
{
private $login, $password;
public function __construct(string $login, string $password)
{
$this->login = $login;
$this->password = $password;
}
public function logIn(): void
{
echo "Send HTTP API request to log in user $this->login with " .
"password $this->password\n";
}
public function logOut(): void
{
echo "Send HTTP API request to log out user $this->login\n";
}
public function createPost($content): void
{
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(string $email, string $password)
{
$this->email = $email;
$this->password = $password;
}
public function logIn(): void
{
echo "Send HTTP API request to log in user $this->email with " .
"password $this->password\n";
}
public function logOut(): void
{
echo "Send HTTP API request to log out user $this->email\n";
}
public function createPost($content): void
{
echo "Send HTTP API requests to create a post in LinkedIn timeline.\n";
}
}
/**
* Клиентский код может работать с любым подклассом SocialNetworkPoster, так как
* он не зависит от конкретных классов.
*/
function clientCode(SocialNetworkPoster $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", "******"));
Output.txt: Результат выполнения
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