===== Реестр (Registry) ===== Хотелось бы начать с этого шаблона. Он немного выбивается из общего ряда, потому что не является порождающим, но в дальнейшем нам потребуется его знание. Итак, реестр – это хэш, доступ к данным у которого осуществляется через статические методы: Нередко можно встретить реестры, реализующие интерфейсы ArrayAccess и/или Iterator, но на мой взгляд, это лишнее. Основное применение реестра – в качестве безопасной замены глобальным переменным. ===== Пул объектов (Object pool) ===== Этот шаблон, по сути, является частным случаем реестра. Пул объектов – это хэш, в который можно складывать инициализированные объекты и доставать их оттуда при необходимости: getId()] = $product; } /** * Возвращает продукт из пула * * @param integer|string $id - идентификатор продукта * @return Product $product */ public static function getProduct($id) { return isset(self::$products[$id]) ? self::$products[$id] : null; } /** * Удаляет продукт из пула * * @param integer|string $id - идентификатор продукта * @return void */ public static function removeProduct($id) { if (array_key_exists($id, self::$products)) { unset(self::$products[$id]); } } } class Product { /** * @var integer|string */ protected $id; public function __construct($id) { $this->id = $id; } /** * @return integer|string */ public function getId() { return $this->id; } } /* * ===================================== * USING OF OBJECT POOL * ===================================== */ Factory::pushProduct(new Product('first')); Factory::pushProduct(new Product('second')); print_r(Factory::getProduct('first')->getId()); // first print_r(Factory::getProduct('second')->getId()); // second ===== Одиночка (Singleton) ===== Наверное, один из самых популярных шаблонов. Как правило, его все запоминают первым. А ещё при поиске работы про него очень любят спрашивать на собеседованиях. Вот самый простой пример: a = 1; $secondProduct->a = 2; print_r($firstProduct->a); // 2 print_r($secondProduct->a); // 2 Принцип синглтона прост, как пять копеек. Для того, чтобы обеспечить существование только одного экземпляра класса Product, мы закрыли все магические методы для создания экземпляра класса, клонирования и сериализации. Единственный возможный способ получить объект – воспользоваться статическим методом Product::getInstance(). При первом обращении класс сам создаст экземпляр себя и положит его в статическое свойство Product::$instance. При последующих обращениях, в рамках выполнения скрипта, метод будет нам возвращать тот же, ранее созданный, экземпляр класса. Я добавил в класс открытое свойство $a, чтобы продемонстрировать работу одиночки. В данном примере можно увидеть, что и $firstProduct, и $secondProduct – есть ни что иное, как ссылка на один и тот же объект. ===== Пул одиночек (Multiton) ===== Возможно, кому-то захочется использовать множество различных синглтонов в своём проекте. Тогда, наверное, стоит отделить логику шаблона от конкретной реализации. Давайте попробуем скрестить шаблоны «Одиночка» и «Пул объектов»: a[] = 1; SecondProduct::getInstance()->a[] = 2; FirstProduct::getInstance()->a[] = 3; SecondProduct::getInstance()->a[] = 4; print_r(FirstProduct::getInstance()->a); // array(1, 3) print_r(SecondProduct::getInstance()->a); // array(2, 4) Итак, для добавления нового класса-одиночки нам просто нужно унаследовать его от класса Factory. В примере мы создали два таких класса и проверили, что у каждого из этих классов свой единственный экземпляр. Я не случайно разбил общую логику на два абстрактных класса. Теперь давайте ещё немного усложним пример. Позволим создавать несколько одиночек для каждого класса, отличающихся уникальным идентификатором. new $className($id), ]; } return self::$instances[$className][$id]; } /** * Удаляет экземпляр класса, из которого вызван * * @param integer|string $id - уникальный идентификатор одиночки. Если не указан, все экземпляры класса будут удалены * @return void */ final public static function removeInstance($id = null) { $className = static::getClassName(); if (isset(self::$instances[$className])) { if (is_null($id)) { unset(self::$instances[$className]); } else { if (isset(self::$instances[$className][$id])) { unset(self::$instances[$className][$id]); } if (empty(self::$instances[$className])) { unset(self::$instances[$className]); } } } } protected function __construct($id) { } } /* * ===================================== * USING OF MULTITON * ===================================== */ /** * Первый пул одиночек */ class FirstFactory extends RegistryFactory { public $a = []; } /** * Второй пул одиночек */ class SecondFactory extends FirstFactory { } // Заполняем свойства одиночек FirstFactory::getInstance('FirstProduct')->a[] = 1; FirstFactory::getInstance('SecondProduct')->a[] = 2; SecondFactory::getInstance('FirstProduct')->a[] = 3; SecondFactory::getInstance('SecondProduct')->a[] = 4; FirstFactory::getInstance('FirstProduct')->a[] = 5; FirstFactory::getInstance('SecondProduct')->a[] = 6; SecondFactory::getInstance('FirstProduct')->a[] = 7; SecondFactory::getInstance('SecondProduct')->a[] = 8; print_r(FirstFactory::getInstance('FirstProduct')->a); // array(1, 5) print_r(FirstFactory::getInstance('SecondProduct')->a); // array(2, 6) print_r(SecondFactory::getInstance('FirstProduct')->a); // array(3, 7) print_r(SecondFactory::getInstance('SecondProduct')->a); // array(4, 8) Примерно по такому принципу работают некоторые ORM, позволяя хранить уже загруженные и инициализированные модели. А теперь, пока ещё не слишком поздно, верну мечтателей с небес на землю. Шаблон Одиночка и его продвинутые братья, несомненно, могут быть полезны, но не надо забываться и лепить его где нужно и где не нужно. Напомню (или поведаю), что есть такой антипаттерн, «Одиночество» (Singletonitis), который как раз заключается в неуместном использовании синглтонов. Так для чего же нам этот шаблон? Самый распространённый пример – соединение с базой данных, которое создаётся один раз и используется на протяжении работы скрипта. А ещё во многих фреймворках реестр делают одиночкой и используют его, как объект, а не как класс со статическими методами. ===== Фабричный метод (Factory method) ===== А теперь предлагаю немного понизить градус и снова вернуться к истокам. Допустим, мы знаем, что бывают фабрики, производящие какой-то свой продукт. Нам не важно, как именно фабрика делает этот продукт, но мы знаем, что у любой фабрики есть один универсальный способ попросить его: getProduct(); $factory = new SecondFactory(); $secondProduct = $factory->getProduct(); print_r($firstProduct->getName()); // The first product print_r($secondProduct->getName()); // Second product В данном примере метод getProduct() является фабричным. ===== Абстрактная фабрика (Abstract Factory) ===== Бывает ситуация, когда у нас есть несколько однотипных фабрик и мы хотим инкапсулировать логику выбора, какую из фабрик использовать для той или иной задачи. Тут-то нам на помощь и приходит этот шаблон. getProduct(); Config::$factory = 2; $secondProduct = AbstractFactory::getFactory()->getProduct(); print_r($firstProduct->getName()); // The first product from the first factory print_r($secondProduct->getName()); // Second product from second factory Как видно из примера, нам не приходится заботится о том, какую фабрику взять. Абстрактная фабрика сама проверяет настройки конфигурации и возвращает подходящую фабрику. Разумеется, вовсе не обязательно абстрактная фабрика должна руководствоваться файлу конфигурации. Логика выбора может быть любой. ===== Отложенная инициализация (Lazy Initialization) ===== А вот вам ещё одна интересная ситуация. Представьте, что у вас есть фабрика, но вы не знаете, какая часть её функционала вам потребуется, а какая – нет. В таких случаях необходимые операции выполнятся только если они нужны и только один раз: firstProduct) { $this->firstProduct = new FirstProduct(); } return $this->firstProduct; } /** * Возвращает продукт * * @return Product */ public function getSecondProduct() { if (!$this->secondProduct) { $this->secondProduct = new SecondProduct(); } return $this->secondProduct; } } /** * Первый продукт */ class FirstProduct implements Product { /** * Возвращает название продукта * * @return string */ public function getName() { return 'The first product'; } } /** * Второй продукт */ class SecondProduct implements Product { /** * Возвращает название продукта * * @return string */ public function getName() { return 'Second product'; } } /* * ===================================== * USING OF LAZY INITIALIZATION * ===================================== */ $factory = new Factory(); print_r($factory->getFirstProduct()->getName()); // The first product print_r($factory->getSecondProduct()->getName()); // Second product print_r($factory->getFirstProduct()->getName()); // The first product При первом вызове метода, фабрика создаёт объект и сохраняет его в себя. При повторном вызове – возвращает уже готовый объект. Если бы мы не вызвали метод, объект бы не создался вовсе. Признаю, в данном примере мало смысла. Здесь использование этого шаблона не оправдано. Я просто хотел показать его смысл. А теперь представьте, что создание объекта требует сложных вычислений, многократных обращений к базе данных, да и ресурсов кушает массу. Весьма хороший повод обратить внимание на этот шаблон. ===== Прототип (Prototype) ===== Некоторые объекты приходится создавать многократно. Есть смысл сэкономить на их инициализации, особенно, если инициализация требует времени и ресурсов. Прототип – это заранее инициализированный и сохранённый объект. В случае необходимости он клонируется: product = $product; } /** * Возвращает новый продукт путём клонирования * * @return Product */ public function getProduct() { return clone $this->product; } } /** * Продукт */ class SomeProduct implements Product { public $name; } /* * ===================================== * USING OF PROTOTYPE * ===================================== */ $prototypeFactory = new Factory(new SomeProduct()); $firstProduct = $prototypeFactory->getProduct(); $firstProduct->name = 'The first product'; $secondProduct = $prototypeFactory->getProduct(); $secondProduct->name = 'Second product'; print_r($firstProduct->name); // The first product print_r($secondProduct->name); // Second product Как видно из примера мы создали два никак не связанных объекта. Строитель (Builder) Ну и последний на сегодня шаблон – строитель. Он полезен, когда мы хотим инкапсулировать создание сложного объекта. Мы просто расскажем фабрике, какому строителю доверить создание продукта: name = $name; } /** * @return string */ public function getName() { return $this->name; } } /** * Какая-то фабрика */ class Factory { /** * @var Builder */ private $builder; /** * @param Builder $builder */ public function __construct(Builder $builder) { $this->builder = $builder; $this->builder->buildProduct(); } /** * Возвращает созданный продукт * * @return Product */ public function getProduct() { return $this->builder->getProduct(); } } /** * Какой-то строитель */ abstract class Builder { /** * @var Product */ protected $product; /** * Возвращает созданный продукт * * @return Product */ final public function getProduct() { return $this->product; } /** * Создаёт продукт * * @return void */ public function buildProduct() { $this->product = new Product(); } } /** * Первый строитель */ class FirstBuilder extends Builder { /** * Создаёт продукт * * @return void */ public function buildProduct() { parent::buildProduct(); $this->product->setName('The product of the first builder'); } } /** * Второй строитель */ class SecondBuilder extends Builder { /** * Создаёт продукт * * @return void */ public function buildProduct() { parent::buildProduct(); $this->product->setName('The product of second builder'); } } /* * ===================================== * USING OF BUILDER * ===================================== */ $firstDirector = new Factory(new FirstBuilder()); $secondDirector = new Factory(new SecondBuilder()); print_r($firstDirector->getProduct()->getName()); // The product of the first builder print_r($secondDirector->getProduct()->getName()); // The product of second builder Итак, мы рассмотрели 9 шаблонов проектирования. Это довольно длинная статья. Поэтому хотелось бы узнать ваше мнение. Есть ли смысл в проделанной работе и стоит ли мне завершить цикл, рассказав о структурных шаблонах и шаблонах поведения? Весь опубликованный код можно найти и на [[https://github.com/Chifek/php-design-patterns/tree/master/Creational|гитхабе]]