Шаблон проектирования Реестр
Дальнейшим развитием шаблона “Одиночка” (Singleton) является шаблон проектирования Реестр (Registry). Основное назначение этого шаблона проектирования – это организация глобального хранилища с единственной точкой доступа. Самая частая реализация Реестра – это обычный шаблон типа Singleton с единственным статическим полем-массивом, в котором хранятся глобальные объекты.
Начнем рассмотрение этого шаблона с конца – с результатов его внедрения. Например, предположим, что мы используем Реестр для хранения объекта работы с БД. В самом начале работы скрипта, мы производим инициализацию объекта БД, и сохраняем этот объект в реестре:
… $DB = new DB($host, $username, $password, $dbname); Registry::getInstance()->set('DB', $DB); …
После этого, в любом участке кода программы, можно достать этот объект так:
… $DB = Registry::getInstance()->get(‘DB’); $users = $DB->select('SELECT * FROM users ORDER BY login ASC'); …
Для проектов среднего и больших размеров, такая практика находит широкое применение.
Что касается базового функционала шаблона проектирования Реестр, то на этом все. Но, на самом деле, существуют еще как минимум 3 вида усовершенствований этого шаблона, каждый из которых решает проблемы, часто встречающиеся во время внедрения шаблона проектирования Registry.
Во-первых – это реализация класса Registry как расширения класса ArrayObject из стандартной библиотеки php (Standart PHP Library или SPL), появившейся в Php5.
Такой подход дает ряд преимуществ при работе с шаблоном проектирования реестр. Например, работать с объектом можно будет как с ассоциативным массивом:
… $reg = Registry::getInstance(); $DB = $reg['DB']; …
Кроме того, в классе ArrayObject реализован интерфейс для работы с итератором, используя который, можно легко пробежаться по всем элементам массива внутри программы, если это необходимо.
Во-вторых – это возможность сохранения сигнатур реестра и их восстановления. Этот прием оказывается очень полезным при написании unit-тестов для большого проекта.
Вот пример реализации этого подхода для php4:
class Registry { var $_cache_stack; function Registry() { $this->_cache_stack = array(array()); } function set($key, &$item) { $this->_cache_stack[0][$key] = &$item; } function &get($key) { return $this->_cache_stack[0][$key]; } function isEntry($key) { return ($this->get($key) !== null); } function &getInstance() { static $registry = false; if (!$registry) { $registry = new Registry(); } return $registry; } function save() { array_unshift($this->_cache_stack, array()); if (!count($this->_cache_stack)) { trigger_error('Registry lost'); } } function restore() { array_shift($this->_cache_stack); } }
В момент, перед возможной модификацией реестра, он сохраняется так:
… $reg = Registry::getInstance(); $reg->save(); …
А после этих операций, возвращается его предыдущая копия:
… $reg = Registry::getInstance(); $reg->restore(); …
Третий вид усовершенствования касается автоматизации загрузки нужных классов и интеграции механизма lazy initialization. Другими словами, если в программе Вы пишете что-то вроде этого:
… $config = Registry::get('Config'); …
То, даже если этому вызову не предшествовала инициализация объекта класса Config, то она будет сделана во время первого вызова (lazy initialization).
Такое удобство, однако, имеет свою стоимость. В этом случае оно компенсируется двумя неудобствами. Во-первых, при именовании классов, необходимо следовать некоторым соглашениям (для корректной работы механизма автозагрузки классов). Это необходимо для того, чтобы по имени вызываемого объекта, можно было определить, из какого файла нужно подгрузить класс.
Вторым недостатком в этом случае, на мой взгляд, является то, что, применяя такой подход по отношению к реестру, либо его область применения ограничивается классами с пустыми конструкторами (конструкторы без параметров, по крайней мере), либо значительно усложняется механизм обращения к объектам из реестра.
Вспомним первый пример для работы с БД: у нас есть конструктор объекта DB, у которого в роли параметров выступают реквизиты доступа к БД (хост, пользователь, пароль, имя базы). Исходя из этого, давайте представим, как будет происходить инициализация объекта БД при первом обращении к него через реестр:
… $DB = Registry::get('DB'); $users = $DB->select('SELECT * FROM users …'); …
В данном случае будет произведена операция lazy initialization, но как передать параметры в конструктор в этом случае? Либо данные параметры нужно хардкодить внутрь класса, либо передавать их каждый раз при вызове метода Registry::get().
Если кто-то видит нормальное решение этой проблемы, то буду раз его услышать
P.S.: Хорошую реализацию этого шаблона проектирования можете найти в Zend Framework – там он называется Zend_Registry.
P.P.S.: В течении последних 4-5 дней были проблемы с RSS-лентой блога из-за того, что, сервер хостинга, заблокировал сервер feedburner`а, из-за чего лента была недоступна нашим читателям. Сейчас проблема разрешилась, так что подписывайтесь все обратно, кто отписался
А пока все. Удачи.
Я что-то пропустил, или про
var $_cache_stack;
не было никаких разъяснений?
Можете ли вы пояснить, как при реализации этого класса реализовать кеширование? Я ведь так понимаю эта переменная именно для него предназначена.
Честно говоря, необходимость lazy initialization элементов реестра у меня вызывает большое сомнение. Обычно в реестре хранятся глобальные объекты приложения, такие как конфиг, коннект к БД, логгер и пр. И практически все они требуют инициализации параметрами (либо через конструктор, либо вызовом методов). Можно, конечно, параметры инициализации перенести в собственный класс, но большинство таких параметров хранятся во внешних ресурсах, что требует еще и реализации механизмов доступа к таким ресурсам.
В общем, я считаю, что lazy initialization в реестре ненужен.
2 Алексей Качаев: $_cache_stack используется для хранения сигнатур реестра по принципу стека (LIFO - Last in - First out). Во втором усовершенствовании я про это написал немного, но и так в принципе понятно (реализация класса тоже есть в посте).
2 Олег Лобач: Сойдемся на этом 😉
Очень хороший шаблон проектирования, лично я даже не представляю как без него можно жить - куча глобальных переменных и прочии некрасивости
>В общем, я считаю, что lazy initialization в реестре ненужен.
нужен, особенно в больших системах, просто нужно зрание передать данные для конструктора.
Я думаю необходимые парамерты вы можете передавать массивом как необязательный параметр,
# $DB = Registry::get(‘DB’, array(‘root,’pasee’,…));
# $users = $DB->select(‘SELECT * FROM users …’);
А в методе get, парсить массив и передавать далее конструктору.
Я широко использую этот паттерн. Но для избежания коллизий или для упрощения колективной разработки я ввел возможность устанавливать блокировку на повторную запись.
Если запись происходит - прерывание с предложеием сменить ключ.
Ище в момент вызова объекта синглетоном фиксируется имя класса (или файла) из которого произошел вызов. Любые обращения к переменным фиксируются - в любой точке приложения можно отследить что происходило с какой-либо переменной.
Всегда делаю четкий функционал шаблона registry, с документированием, нет необходимости в инициализации по переменной:
$DB = Registry::getDB(array(”,”,…));