- Суть паттерна
Фабричный метод - это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.
- Проблема
Представьте, что вы создаёте программу управления грузовыми перевозками. Сперва вы рассчитываете перевозить товары только на автомобилях. Поэтому весь ваш код работает с объектами класса Грузовик. В какой-то момент ваша программа становится настолько известной, что морские перевозчики выстраиваются в очередь и просят добавить поддержку морской логистики в программу.
Отличные новости, правда?! Но как насчёт кода? Большая часть существующего кода жёстко привязана к классам Грузовиков. Чтобы добавить в программу классы морских Судов, понадобится перелопатить всю программу. Более того, если вы потом решите добавить в программу ещё один вид транспорта, то всю эту работу придётся повторить.
В итоге вы получите ужасающий код, наполненный условными операторами, которые выполняют то или иное действие, в зависимости от класса транспорта.
- Решение
Паттерн Фабричный метод предлагает создавать объекты не напрямую, используя оператор 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