Данный паттерн, как и 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() {
    }
}
 
?>

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

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

Оригинал

Комментарии

comments powered by Disqus