📜 ⬆️ ⬇️

PHP design patterns. Part 1. Generating

I’m not going to argue about a topic that’s broken into holes ... Probably, for experienced developers, my article will be of little use. I would recommend it for reading to those who only began to realize that something was missing from its code, and that it was ripe for an understanding of this distant concept - “patterns”. I remember that for quite a long time I was confused in templates, sometimes not even understanding how one differs from another. This fact became the basis for my article. Examples in it will not be real. They will be abstract and as simple as possible. However, I will try to keep all the examples in the same context so that you can clearly see the differences in their use in the same situation. I will not load classes with unnecessary functionality so that you can understand exactly which part of the code is directly related to the template. The main heroes of the examples will be Factory (factory) and Product (the product produced by this factory). Take this attitude as a starting point. Perhaps in some examples this will not be very appropriate, but it’s very clear ...

The article will be divided into several parts. In each, I will talk about a new type of design pattern. Anyone who may be interested in this topic, please under the cat.

Generating design patterns


With your permission, I will not retell for the hundredth time who they are, these generating patterns ... I just leave here a link to Wikipedia . Brevity is the soul of wit. Therefore, I immediately offer examples.
')

Registry (Registry)


I would like to start with this template. It is a little out of line, because it is not generating, but in the future we will need his knowledge. So, the registry is a hash whose data is accessed through static methods:

Example 1
<?php /** *  */ class Product { /** * @var mixed[] */ protected static $data = array(); /** *     * * @param string $key * @param mixed $value * @return void */ public static function set($key, $value) { self::$data[$key] = $value; } /** *       * * @param string $key * @return mixed */ public static function get($key) { return isset(self::$data[$key]) ? self::$data[$key] : null; } /** *       * * @param string $key * @return void */ final public static function removeProduct($key) { if (array_key_exists($key, self::$data)) { unset(self::$data[$key]); } } } /* * ===================================== * USING OF REGISTRY * ===================================== */ Product::set('name', 'First product'); print_r(Product::get('name')); // First product 

Often you can find registries that implement the interfaces ArrayAccess and / or Iterator, but in my opinion, this is unnecessary. The main use of the registry is as a safe substitute for global variables.

Object Pool


This template, in fact, is a special case of the registry. The object pool is a hash into which you can add initialized objects and retrieve them from there if necessary:

Example 2
 <?php /** *   */ class Factory { /** * @var Product[] */ protected static $products = array(); /** *     * * @param Product $product * @return void */ public static function pushProduct(Product $product) { self::$products[$product->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


Probably one of the most popular templates. As a rule, everyone remembers it first. And when looking for a job, people like to ask about him at job interviews. Here is the simplest example:

Example 3
 <?php /** *  */ final class Product { /** * @var self */ private static $instance; /** * @var mixed */ public $a; /** *    * * @return self */ public static function getInstance() { if (!(self::$instance instanceof self)) { self::$instance = new self(); } return self::$instance; } /** *   */ private function __construct() { } /** *   */ private function __clone() { } /** *   */ private function __sleep() { } /** *   */ private function __wakeup() { } } /* * ===================================== * USING OF SINGLETON * ===================================== */ $firstProduct = Product::getInstance(); $secondProduct = Product::getInstance(); $firstProduct->a = 1; $secondProduct->a = 2; print_r($firstProduct->a); // 2 print_r($secondProduct->a); // 2 

The singleton principle is as simple as five kopecks. In order to ensure the existence of only one instance of the Product class, we have closed all magic methods for creating an instance of the class, for cloning and serializing. The only possible way to get an object is to use the static method Product :: getInstance () . On the first call, the class itself will create an instance of itself and put it in the static property of Product :: $ instance . On subsequent calls, as part of script execution, the method will return the same, previously created, instance of the class.

I added the open property $ a to the class to demonstrate how the loner works. In this example, you can see that both $ firstProduct and $ secondProduct are nothing more than a reference to the same object.

Pool of singles (Multiton)


Maybe someone wants to use a lot of different singltonov in your project. Then, it is probably worth separating the logic of the template from the specific implementation. Let's try to cross the “Singleton” and “Object Pool” templates:

Example 4.1
 <?php /** *     */ abstract class FactoryAbstract { /** * @var array */ protected static $instances = array(); /** *   ,    * * @return static */ public static function getInstance() { $className = static::getClassName(); if (!(self::$instances[$className] instanceof $className)) { self::$instances[$className] = new $className(); } return self::$instances[$className]; } /** *   ,    * * @return void */ public static function removeInstance() { $className = static::getClassName(); if (array_key_exists($className, self::$instances)) { unset(self::$instances[$className]); } } /** *     * * @return string */ final protected static function getClassName() { return get_called_class(); } /** *   */ protected function __construct() { } /** *   */ final protected function __clone() { } /** *   */ final protected function __sleep() { } /** *   */ final protected function __wakeup() { } } /** *    */ abstract class Factory extends FactoryAbstract { /** *   ,    * * @return static */ final public static function getInstance() { return parent::getInstance(); } /** *   ,    * * @return void */ final public static function removeInstance() { parent::removeInstance(); } } /* * ===================================== * USING OF MULTITON * ===================================== */ /** *   */ class FirstProduct extends Factory { public $a = []; } /** *   */ class SecondProduct extends FirstProduct { } //    FirstProduct::getInstance()->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) 

So, to add a new lone class, we just need to inherit it from the Factory class. In the example, we created two such classes and checked that each of these classes has its own single instance.

I did not accidentally break the general logic into two abstract classes. Now let's complicate the example a little more. Let us create several singles for each class with a unique identifier.

Example 4.2
 <?php /** *     */ abstract class RegistryFactory extends FactoryAbstract { /** *   ,    * * @param integer|string $id -    * @return static */ final public static function getInstance($id) { $className = static::getClassName(); if (isset(self::$instances[$className])) { if (!(self::$instances[$className][$id] instanceof $className)) { self::$instances[$className][$id] = new $className($id); } } else { self::$instances[$className] = [ $id => 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) 

Approximately by this principle some ORM work, allowing to store already loaded and initialized models.

And now, before it is too late, I will return the dreamers from heaven to earth. The Singleton template and its advanced brothers can certainly be useful, but don’t forget to sculpt it where you need it and where you don’t need it. Let me remind you (or tell) that there is such an anti-pattern, “Loneliness” (Singletonitis), which is precisely the inappropriate use of singletons. So why do we need this template? The most common example is a connection to a database, which is created once and is used throughout the script. And in many frameworks, the registry is made a loner and is used as an object, and not as a class with static methods.

Factory method


And now I propose to lower the degree a bit and go back to basics. Suppose we know that there are factories producing some kind of their own product. We don’t care how the factory makes this product, but we know that any factory has one universal way to ask for it:

Example 5
 <?php /** *  */ interface Factory { /** *   * * @return Product */ public function getProduct(); } /** *  */ interface Product { /** *    * * @return string */ public function getName(); } /** *   */ class FirstFactory implements Factory { /** *   * * @return Product */ public function getProduct() { return new FirstProduct(); } } /** *   */ class SecondFactory implements Factory { /** *   * * @return Product */ public function getProduct() { return new 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 FACTORY METHOD * ===================================== */ $factory = new FirstFactory(); $firstProduct = $factory->getProduct(); $factory = new SecondFactory(); $secondProduct = $factory->getProduct(); print_r($firstProduct->getName()); // The first product print_r($secondProduct->getName()); // Second product 

In this example, the getProduct () method is factory-made.

Abstract Factory


It happens that we have several factories of the same type and we want to encapsulate the logic of choosing which of the factories to use for a particular task. This is where this template comes to our rescue.

Example 6
 <?php /** * -   */ class Config { public static $factory = 1; } /** * -  */ interface Product { /** *    * * @return string */ public function getName(); } /** *   */ abstract class AbstractFactory { /** *   * * @return AbstractFactory -   * @throws Exception */ public static function getFactory() { switch (Config::$factory) { case 1: return new FirstFactory(); case 2: return new SecondFactory(); } throw new Exception('Bad config'); } /** *   * * @return Product */ abstract public function getProduct(); } /* * ===================================== * FIRST FAMILY * ===================================== */ class FirstFactory extends AbstractFactory { /** *   * * @return Product */ public function getProduct() { return new FirstProduct(); } } /** *    */ class FirstProduct implements Product { /** *    * * @return string */ public function getName() { return 'The product from the first factory'; } } /* * ===================================== * SECOND FAMILY * ===================================== */ class SecondFactory extends AbstractFactory { /** *   * * @return Product */ public function getProduct() { return new SecondProduct(); } } /** *    */ class SecondProduct implements Product { /** *    * * @return string */ public function getName() { return 'The product from second factory'; } } /* * ===================================== * USING OF ABSTRACT FACTORY * ===================================== */ $firstProduct = AbstractFactory::getFactory()->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 

As you can see from the example, we don’t have to worry about which factory to take. The abstract factory itself checks the configuration settings and returns the appropriate factory. Of course, not necessarily an abstract factory should be guided by the configuration file. The logic of choice can be any.

Delayed initialization (Lazy Initialization)


And here's another interesting situation. Imagine that you have a factory, but you do not know what part of its functionality you need, and what part you don’t. In such cases, the necessary operations will be performed only if they are needed and only once:

Example 7
 <?php /** * -  */ interface Product { /** *    * * @return string */ public function getName(); } class Factory { /** * @var Product */ protected $firstProduct; /** * @var Product */ protected $secondProduct; /** *   * * @return Product */ public function getFirstProduct() { if (!$this->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 

When the method is first called, the factory creates an object and saves it to itself. When you call again, it returns a ready object. If we had not called the method, the object would not have been created at all. I admit, in this example there is little meaning. Here the use of this template is not justified. I just wanted to show its meaning. Now imagine that the creation of an object requires complex calculations, repeated calls to the database, and even resources eats a lot. A very good reason to pay attention to this template.

Prototype (Prototype)


Some objects have to be created many times. It makes sense to save on their initialization, especially if the initialization requires time and resources. A prototype is a pre-initialized and saved object. If necessary, it is cloned:

Example 8
 <?php /** * -  */ interface Product { } /** * -  */ class Factory { /** * @var Product */ private $product; /** * @param Product $product */ public function __construct(Product $product) { $this->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 

As you can see from the example, we created two unrelated objects.

Builder


Well, the last template for today is a builder. It is useful when we want to encapsulate the creation of a complex object. We will simply tell the factory which builder to entrust the creation of the product:

Example 9
 <?php /** * -  */ class Product { /** * @var string */ private $name; /** * @param string $name */ public function setName($name) { $this->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 

So, we looked at 9 design patterns. This is a fairly long article. Therefore, I would like to know your opinion. Does it make sense in the work done and should I complete the cycle by talking about structural patterns and patterns of behavior?

All published code can be found on github .

Source: https://habr.com/ru/post/214285/


All Articles