Паттерн: Registry

Постараюсь расказать о моей реализации паттерна Registry под php. Registry — это ООП замена глобальным переменным, предназначеная для хранения данных и передачи их между модулями системы. Соответственно, его наделяют стандартными свойствами — запись, чтение, удаление. Вот типовая реализация.

<?php
class Registry {
    static private $data = array();
    static public function set($key, $value) {
        self::$data[$key] = $value;
    }
    static public function get($key) {
        return isset(self::$data[$key]) ? self::$data[$key] : null;
    }
    static public function remove($key) {
        if ( isset(self::$data[$key]) ) {
            unset(self::$data[$key]);
        }
    }
}
?>

Ну и таким образом получаем тупую замену методов $key = $value — Registry::set($key, $value) $key — Registry::get($key) unset($key) — remove Registry::remove($key) Только становится непонятно — а зачем этот лишний код. Итак, научим наш класс делать то, что не умеют глобальные переменные.  Добавим в него перчика.

<?php
class Amdy_Registry {
    static protected $data;
    static protected $lock = array();
    static public function set($key, $value) {
        if ( !self::hasLock($key) ) {
            self::$data[$key] = $value;
        } else {
            throw new Exception("переменная '$key' заблокирована для изменений");
        }
    }
    static public function get($key, $default = null) {
        if ( self::has($key) ) {
            return self::$data[$key];
        } else {
            return $default;
        }
    }
    static public function remove($key) {
        if ( self::has($key) && self::hasLock($key) ) {
            unset(self::$data[$key]);
        }
    }
    static public function has($key) {
        return isset(self::$data[$key]);
    }
    static public function lock($key) {
        self::$lock[$key] = true;
    }
    static public function hasLock($key) {
        return isset(self::$lock[$key]);
    }
    static public function unlock($key) {
        if ( self::hasLock($key) ) {
        	unset(self::$lock[$key]);
        }
    }
}
$test = 'test';
var_dump(Amdy_Registry::get('test'));
Amdy_Registry::set('test', $test);
var_dump(Amdy_Registry::get('test'));
Amdy_Registry::lock('test');
try {
    Amdy_Registry::set('test', 'not work');
} catch (Exception $e) {
    var_dump($e->getMessage());
}
Amdy_Registry::unlock('test');
var_dump(Amdy_Registry::get('test'));
?>

К типичным задачам паттерна, я добавил возможность блокировки переменной от изменений,, это очень удобно на больших проектах, случайно не всунешь ничего. Например, удобно для работы с бд
define(‘DB_DNS’, ‘mysql:host=localhost;dbname=<SOME_DB>’);
define(‘DB_USER’,  ‘<SOME_USER>’);
define(‘DB_PASSWORD’,  ‘<SOME_PASSWORD>’);
define(‘DB_HANDLE’);

Amdy_Regisrtry::set(DB_HANDLE, new PDO(DB_DNS, DB_USER, DB_PASSWORD) );
Amdy_Registry::lock(DB_HANDLE);

Сейчас пояснения по коду, чтобы хранить данные, мы используем статическую переменную $data, в переменной $lock хранятся данные о заблокированых для изменения ключах. В сетере мы проверяем залочена ли переменная и изменяем или добавляем её в регистр. При удалении, также проверяем лок, гетер остаётся неизменным, за исключением опционального параметра по умолчанию. Ну и стоит обратить внимание на обработку исключений, которой почему-то редко пользуются, кстати, у меня уже есть черновик по исключениям, ждите статью. Чуть ниже черновой код для тестирования, вот и статью о тестировании, тоже бы не мешало настрочить, хотя я и не почетатель TDD.

В следующей статье ещё расширим функционал, дабавив инициализацию данных и реализуем «ленивость».

Паттерн: Registry: 8 комментариев

  1. bf

    Задумка впринципе неплоха, но по-моему вы никогда в глаза не видели задач на ооп с применением паттерном там где они и вправду помогают. А это вовсе не язык-парсер, а как минимум мини-игра. Вы высосали проблему из пальца и героически е решили. Вопрос в лоб? сколько раз за свой программерский опыт вы сталкивались с необходимостью запрета модификации переменной, которая хранит соединение с базой? Аж ни разу. И толку что он локед или нет? Другой прораммер влезет в код, встретит эту фигню, напишет на крутом ООП подходе с умным лицом ->unlock() и задача решена. ДЛя чего это тогда задумывалось? Видимо это реально адвансед, надо пойти обдумать.
    Мамочки, эти люди «думаю паттернами».

  2. AmdY

    Блог не зря называется «извращения кодера» ;), здесь эксперименты. Но и на практике я не раз сталкивался с ошибками, причиной которой была перезапись переменной, сталкивался с открытием лишних соединений с бд и даже сталкивался с перезаписью коннекта к базе, в проекте, где использовалось два хранилища (mysql и sqlite), но, они хранились не в реестре, а в глобальных переменных, вот это проблема.
    Вот именно, когда вывалится исключение, у программиста появится возможность подумать, действительно ли ему нужно перезаписать значение. Задача не запрещать, а предупреждать.
    В программировании уже давно есть возможность создавать константы и константные объекты, т.е. создавать данные, которые не должны изменяться после инициализации, здесь что-то подобное, но гибче и не так строго, так же повсеместно используется блокировки файлов, таблиц и записей в БД.

    1. admin Автор записи

      мне недавно очень грамотный человек разъяснил, что всё же в классической реализации есть одна большая неточность. самое удивительное, что даже у Фаулера это понятно, но где-то по дороге паттерн потеряли, точно так же как и MVC.
      Важной особенностью Registry является то, что у него не должно быть метода set, а должен быть метод add, который добавляет элемент только, если его ещё не положили в реестр. Тем самым гарантируя, что положенные в лукошко пирожки бабушке попадут в том виде, в котором их испекла мама красной шапочки.

  3. President

    Конечно дико извиняюсь, но на 20 строке перед self::hasLock($key) забыто отрицание.

  4. Владислав

    Ребята, я вообще отказался от него пять минут назад, когда понял, что намного эффективнее вызывать методы классов по принципу LazyLoading. Т. е. я просто не прописываю их обьекты в Реестр, а когда мне нужно вызываю их статический метод через __autoload и это круто!

    1. AmdY Автор записи

      Одно другому не мешает, в реестр можно класть через анонимку
      Amdy_Registry::set(‘foo’, function() {
      return new Bar();
      });
      и чуть модифицировать get
      Но вообще пример в статье плохой, нельзя делать статичный класс для реестра. Так как реестр это контейнер и их может быть несколько с разными данными. Это не замена глобалам, как мне казалось в 2009.
      $one = new Registry();
      $two = new Registry();
      $one->set(‘foo’, 1);
      $second->set(‘foo’, 2);

  5. Александр

    По-моему, надежней использовать array_key_exists вместо isset, некоторые версии php ругаются, что ключ не определен

Добавить комментарий

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