Xpoint
   [напомнить пароль]

Подмена одного объекта другим

Метки: [без меток]
2009-03-13 19:08:09 [обр] Антон Иконников(2/30)[досье]

Вот есть у меня набор плагинов в CMS, которые, в целом, друг о друге ничего не знают. CMS устроена таким образом, что плагины вызываются по мере необходимости при распарсивнии шаблона.

Одним из первых обычно вызывается плагин Auth, который в рамках своей работы создает объект user_obj класса User, содержащего всякую разную информацию о текущем пользователе и методы, позволяющие эту информацию вытаскивать. Этот объект сохраняется в хранилище и другие плагины могут его использовать.

В какой-то момент вызывается плагин XXX, которому не хватает возможностей объекта user_obj, но при этом имеющиеся возможности также нужны.

Собственно вопрос в том, как грамотно расширить возможности этого user_obj?

Первый вариант, который мне пришел в голову: в плагине XXX проверять какому классу принадлежит user_obj и в случае, когда он класса User заменять его на новый объект производного от User класса, расширяющего функциональность. Но этот вариант плохой, поскольку создание user_obj - затраное действие и повторять его плохо.

Что можете посоветовать?

спустя 2 часа 58 минут [обр] Thirteensmay(17/157)[досье]
Видится 2 варианта: 1 - создавать дополнительный объект ext_user_obj с дополнительными возможностями, user_obj при этом не трогать, работать с двумя объектами. Если пойти немного дальше то user_obj можно сделать частью ext_user_obj через ссылку. Ну а если необходим совсем один объект то вариант 2: подменять стандартный user_obj расширенным, но с точки зрения затратности не пересоздавать все что относится к стандартной части заново, а просто скопировать это из первоначального объекта, после чего его можно будет прибить. Вообще с точки зрения затратности наиболее оптимальный вариант - промежуточный, т.е. user_obj как часть ext_user_obj доступный через ссылку.
спустя 6 часов [обр] Ali(5/5)[досье]
Есть еще один очень интересный вариант (первый раз встретил его во фреймворке symfony v.1.0), суть его в применении так называемых mixins, т.е. "подмешивание" методов других классов к какому-либо классу. В symfony это применяется как заменитель множественного наследования, также позволяет расширять возможности класса в runtime. Т.е. в итоге объект какого-либо класса начинает обладать свойствами и методами других классов. То же самое используется в Propel и называется behaviours (например countableBehaviour - у объекта появляются методы типа inc(), dec(), getCount() и т.д.). Если интересно - ссылка здесь http://www.symfony-project.org/book/1_0/17-Extending-Symfony
Готовый класс можно взять в исходниках фреймворка.
Можно упростить реализацию и сделать наподобие плагинов Wordpress - по коду развешиваются спец. хуки (do_action(), do_filter()), далее регистрируются списки обработчиков этих хуков, для "навешивания" обработчиков есть функции типа add_action(), add_filter(). Вся доп. логика соответственно реализуется в обработчиках, а do_action() просто вызывает зарегистрированные обработчики по списку с помощью call_user_func_array().
Т.о. можно ставить хуки в конструкторе, в определенных методах (которые предполагается расширять), или использовать магию __call() и пытаться искать неизвестный метод в зарегистрированных обработчиках хуков. Полет фантазии не ограничен.
Оба подхода проверены и работают. Главное, что не создается монстроидальных сильносвязанных объектов, и компоненты легко отделяются и реализуются. Например, в конструкторе создали объект какого-то класса, сохранили ссылку в базовом классе, потом с помощью обработчиков в __call() вызываем его методы, "снаружи" создается полная видимость работы с одним объектом... Плюс расширяющий и расширяемый классы поддаются unit-тестированию (реализуется модный нынче dependency injection).
спустя 9 часов [обр] Антон Иконников(2/30)[досье]

Сделал так:
ext_user_obj - объект отдельного класса (не производного).
При его создании в него передается объект user_obj (если ниче не передавать, то он создастся сам внутри ext_user_obj).
При обращении к несуществующим методам ext_user_obj вызываются через __call() методы user_obj.

Есть ли какие видимые недостатки?

спустя 2 часа 11 минут [обр] Ali(5/5)[досье]
Вроде бы недостатков особых нет, единственное опасение - если другим плагинам надо будет работать с расширенным объектом, то они о нем ничего не знают, а будут ожидать объект стандартного класса User, в котором никаких ссылок на расширенный класс нет (также в вызовах могут быть type hints, которые выбросят ошибку, если им предать объект другого класса).
Если же другой объект использует только один плагин, то в принципе объекты можно даже не связывать. а просто брать User из хранилища...
По сути у вас не расширение, а просто добавление еще одного объекта, т.к. меняется сам класс. Расширение же предполагает сохранение класса объекта.
спустя 30 минут [обр] Антон Иконников(2/30)[досье]

Преполагается, что с расширенным объектом только один плагин будет работать.
Да, действительно получается, что это не расширение, а новый объект, но мне, вроде, большего и не надо.

Спасибо за помощь.

Powered by POEM™ Engine Copyright © 2002-2005