User Tools

Site Tools


php_patterns

This is an old revision of the document!


Реестр (Registry)

Хотелось бы начать с этого шаблона. Он немного выбивается из общего ряда, потому что не является порождающим, но в дальнейшем нам потребуется его знание. Итак, реестр – это хэш, доступ к данным у которого осуществляется через статические методы:

<?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

Нередко можно встретить реестры, реализующие интерфейсы ArrayAccess и/или Iterator, но на мой взгляд, это лишнее. Основное применение реестра – в качестве безопасной замены глобальным переменным.

Пул объектов (Object pool)

Этот шаблон, по сути, является частным случаем реестра. Пул объектов – это хэш, в который можно складывать инициализированные объекты и доставать их оттуда при необходимости:

<?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)

Наверное, один из самых популярных шаблонов. Как правило, его все запоминают первым. А ещё при поиске работы про него очень любят спрашивать на собеседованиях. Вот самый простой пример:

<?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

Принцип синглтона прост, как пять копеек. Для того, чтобы обеспечить существование только одного экземпляра класса Product, мы закрыли все магические методы для создания экземпляра класса, клонирования и сериализации. Единственный возможный способ получить объект – воспользоваться статическим методом Product::getInstance(). При первом обращении класс сам создаст экземпляр себя и положит его в статическое свойство Product::$instance. При последующих обращениях, в рамках выполнения скрипта, метод будет нам возвращать тот же, ранее созданный, экземпляр класса.

Я добавил в класс открытое свойство $a, чтобы продемонстрировать работу одиночки. В данном примере можно увидеть, что и $firstProduct, и $secondProduct – есть ни что иное, как ссылка на один и тот же объект.

Пул одиночек (Multiton)

Возможно, кому-то захочется использовать множество различных синглтонов в своём проекте. Тогда, наверное, стоит отделить логику шаблона от конкретной реализации. Давайте попробуем скрестить шаблоны «Одиночка» и «Пул объектов»:

<?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)

Итак, для добавления нового класса-одиночки нам просто нужно унаследовать его от класса Factory. В примере мы создали два таких класса и проверили, что у каждого из этих классов свой единственный экземпляр.

Я не случайно разбил общую логику на два абстрактных класса. Теперь давайте ещё немного усложним пример. Позволим создавать несколько одиночек для каждого класса, отличающихся уникальным идентификатором.

<?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)
php_patterns.1625636769.txt.gz · Last modified: 2023/09/14 06:06 (external edit)