Pattern Registry – «Реестр»

Данный паттерн, как и Singleton, редко вызывает положительную реакцию со стороны разработчиков, так как порождает те же самые проблемы при тестировании приложений. Тем не менее, ругают, но активно используют. Как и Singleton, шаблон Реестр встречается во многих приложениях и, так или иначе, значительно упрощает решение некоторых задач.

Подобно Одиночке, паттерн Registry вводит объект в глобальную область видимости, позволяя использовать его на любом уровне приложения. О глобальной области видимости в PHP я уже писал в заметке о паттерне Singleton, ввиду чего предлагаю ознакомиться с ней, прежде чем продолжить читать дальше. Там вы найдете ответы на большую часть возможных вопросов.
Паттерн Registry получил распространение в двух вариантах реализации. Одну из них нередко называют «чистым реестром», вторую – «реестр одиночка» (Singleton Registry).

Рассмотрим оба варианта по порядку.




То, что называются «чистым реестром» или просто Registry представляет собой реализацию класса со статическим интерфейсом. Основным отличием от паттерна Singleton является блокирование возможности создания хотя бы одного экземпляра класса. Ввиду этого скрывать магические методы __clone() и __wakeup() за модификатором private или protected нет смысла.

Класс Registry должен иметь два статических метода – геттер и сеттер. Сеттер помещает передаваемый объект в хранилище с привязкой к заданному ключу. Геттер, соответственно, возвращает объект из хранилища. Хранилище – не что иное, как ассоциативный массив ключ – значение.

Для полного контроля над реестром вводят еще один элемент интерфейса – метод, позволяющий удалить объект из хранилища.

<?php

class Registry
{
    /**
     * Registry hash-table
     *
     * @var array
     */
    protected static $_registry = array();

    /**
     * Put item into the registry
     * 
     * @param string $key
     * @param mixed $item
     * @return void
     */
    public static function set($key, $item) {
        if (!array_key_exists($key, self::$_registry)) {
            self::$_registry[$key] = $item;
        }
    }

    /**
     * Get item by key
     * 
     * @param string $key
     * @return false|mixed
     */
    public static function get($key) {
        if (array_key_exists($key, self::$_registry)) {
            return self::$_registry[$key];
        }

        return false;
    }

    /**
     * Remove item from the regisry
     * 
     * @param string $key
     * @return void
     */
    public static function remove($key) {
        if (array_key_exists($key, self::$_registry)) {
            unset(self::$_registry[$key]);
        }
    }

    protected function __construct() {
        
    }
}

?>

Помимо проблем, идентичных паттерну Singleton, выделают еще две:

  • введение еще одного типа зависимости – от ключей реестра;
  • два разных ключа реестра могут иметь ссылку на один и тот же объект

В первом случае избежать дополнительной зависимости невозможно. В какой-то степени мы действительно становимся привязаны к именам ключей.

Вторая проблема решается введением проверки в метод Registry::set():

public static function set($key, $item) {
    if (!array_key_exists($key, self::$_registry)) {
        foreach (self::$_registry as $val) {
            if ($val === $item) {
                throw new Exception('Item already exists');
            }
        }

        self::$_registry[$key] = $item;
    }
}

«Чистый паттерн Registry» порождает еще одну проблему – усиление зависимости за счет необходимости обращения к сеттеру и геттеру через имя класса. Нельзя создать ссылку на объект и работать с ней, как в случае с паттерном Одиночка, когда был доступен такой подход:

$instance = Singleton::getInstance();
$instance->Foo();

Здесь мы имеем возможность сохранить ссылку на экземпляр Singleton, например, в свойстве текущего класса, и работать с ней так, как того требует идеология ООП: передавать в качестве параметра агрегированным объектам или использовать в потомках.

Для разрешения этого вопроса существует реализация Singleton Registry, которую многие не любят за избыточный, как им кажется код. Я думаю, причиной такого отношения является некоторое непонимание принципов ООП или осознанное пренебрежение ими.

<?php

class Registry
{
    static private $_instance = null;
 
    private $_registry = array(); 
 
    static public function getInstance() {
        if (is_null(self::$_instance)) {
            self::$_instance = new self;
        }
 
        return self::$_instance;
    }
 
    static public function set($key, $object) {
        self::getInstance()->_registry[$key] = $object;
    }
 
    static public function get($key) {
        return self::getInstance()->_registry[$key];
    }

    private function __wakeup() {
    }

    private function __construct() {
    }

    private function __clone() {
    }
}

?>

С целью экономии, осознанно опустил блоки комментариев для методов и свойств. Полагаю, в них нет необходимости.

Как я уже говорил, принципиальная разница в том, что теперь появилась возможность сохранить ссылку на объем реестра и не использовать каждый раз громоздкие обращения к статическим методам. Данный вариант мне кажется несколько более правильным. Согласие или не согласие с моим мнением не имеет большого значения, как и само мое мнение. Никакие тонкости реализации не избавляют паттерн от ряда упомянутых минусов.

Комментарии (2)

  1. Виктор

    Вопрос: все, конечно, зависит от того как правильно применять шаблон, но вот эта дополнительная проверка для значений массива, она нужна? Ведь разные ключи, имеющие разный смысл могут иметь одно и тоже значение:

    Registry::set(‘mode’, 1);
    Registry::set(‘debug’, 1);

    Ну, что-то в этом роде.

    • Да, такая проблема существует и я о ней упомянул в заметке. И здесь есть два варианта решения, правда, оба рассчитаны на работу с объектами. Одно описано — это использование проверки на существование объекта в реестре (обход реестра в цикле и проверка строгим равно). А второй — это уже почти вовсе не реестр, а больше Singleton, наследующий идею Registry. То есть создаем класс, реализующий паттерн Singleton, но имеющий метод set(), который может принимать ссылку на объект класса, который мы хотим сделать доступным глобально. Ограничиваем работу так, чтобы принималась только одна ссылка и только определенного типа, то есть мы были бы уверены, что работаем именно с тем объектом, с которым нам необходимо.

      Где это может быть нужно? Например, у нас есть класс, который необходимо сделать доступным глобально, но переписывать его как Singleton нет желания или возможности. Тогда нам поможет такой вот персональный и «одноразовый» реестр.

      Registry::set(‘mode’, 1);

      Такого вообще быть не должно, конечно. Здесь нужно использовать константы, как минимум.

Добавить комментарий для Мурашов Олег Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *