добавление методов класса / объекта в процессе работы
У меня тут наконец дошли руки до разработки пакета PEAR HTML_QuickForm2, пришлось заниматься решением вопроса, вынесенного в заголовок.
Откуда вообще возник вопрос: вывод формы в предыдущей версии пакета осуществлялся классами-Renderer'ами, реализующими паттерн Visitor. В принципе всё это жужжало весьма неплохо, позволяя выводить форму посредством кучи не очень между собой похожих шаблонных движков. Но: не было простого способа расширить функциональность этих самых Renderer'ов, поэтому товарищи, писавшие всякие сложные дополнительные элементы, фактически писали для них свой вывод с шаблонами, блекджеком и шлюхами.
То есть задача стоит следующим образом: портировать Renderer'ы для QuickForm2, при этом дав возможность простым образом расширять их функциональность.
Почему нельзя обойтись наследованием? Пусть у нас есть два дополнительных элемента HTML_QuickForm2_Element_Foo и HTML_QuickForm2_Element_Bar, распространяющихся в отдельных пакетах, при этом каждому из них нужен какой-то специальный вывод. Ну делаем наследование
и садимся в лужу, поскольку объединить их функциональность в одном классе, экземпляр которого нам надо передавать, будет трудновато --- в похапэ нет множественного наследования.
Соответственно надо как-то реализовать систему Plugin'ов, которая позволит добавлять методы на ходу. Теоретически в похапэ 5.3 есть замыкания, но в них нельзя использовать $this и обращаться к защищённым методам и атрибутам. Теоретически также есть расширение runkit, но оно таки нестандартное и сильно уменьшит аудиторию пакета. Остаётся вариант с __call().
Ну и ещё закономерное требование, чтобы была возможность отложенной инициализации: Renderer'ов может быть много, на каждый по plugin'у, а реально использоваться будет, как правило, один. И при этом хотелось бы, чтобы при регистрации нового plugin'а он магически добавлялся в существующие экземпляры класса Renderer'а.
В общем, приняв во внимание все требования, в результате пришёл к следующему дизайну базового класса. Работает оно примерно так:
Эстетическое чувство, впрочем, не совсем успокоилось:
Расскажите, коллеги, кому приходилось решать похожие задачи, к чему пришли?
У меня тут наконец дошли руки до разработки пакета PEAR HTML_QuickForm2, пришлось заниматься решением вопроса, вынесенного в заголовок.
Откуда вообще возник вопрос: вывод формы в предыдущей версии пакета осуществлялся классами-Renderer'ами, реализующими паттерн Visitor. В принципе всё это жужжало весьма неплохо, позволяя выводить форму посредством кучи не очень между собой похожих шаблонных движков. Но: не было простого способа расширить функциональность этих самых Renderer'ов, поэтому товарищи, писавшие всякие сложные дополнительные элементы, фактически писали для них свой вывод с шаблонами, блекджеком и шлюхами.
То есть задача стоит следующим образом: портировать Renderer'ы для QuickForm2, при этом дав возможность простым образом расширять их функциональность.
Почему нельзя обойтись наследованием? Пусть у нас есть два дополнительных элемента HTML_QuickForm2_Element_Foo и HTML_QuickForm2_Element_Bar, распространяющихся в отдельных пакетах, при этом каждому из них нужен какой-то специальный вывод. Ну делаем наследование
Код:
HTML_QuickForm2_Renderer_Default
|
+-- HTML_QuickForm2_Renderer_Default_Foo
\-- HTML_QuickForm2_Renderer_Default_Bar
Соответственно надо как-то реализовать систему Plugin'ов, которая позволит добавлять методы на ходу. Теоретически в похапэ 5.3 есть замыкания, но в них нельзя использовать $this и обращаться к защищённым методам и атрибутам. Теоретически также есть расширение runkit, но оно таки нестандартное и сильно уменьшит аудиторию пакета. Остаётся вариант с __call().
Ну и ещё закономерное требование, чтобы была возможность отложенной инициализации: Renderer'ов может быть много, на каждый по plugin'у, а реально использоваться будет, как правило, один. И при этом хотелось бы, чтобы при регистрации нового plugin'а он магически добавлялся в существующие экземпляры класса Renderer'а.
В общем, приняв во внимание все требования, в результате пришёл к следующему дизайну базового класса. Работает оно примерно так:
PHP:
require_once 'HTML/QuickForm2/Renderer/Default.php';
require_once 'HTML/QuickForm2/Renderer/Plugin.php';
// Приходится наследовать, так как иначе до protected атрибутов и методов не добраться
class HTML_QuickForm2_Renderer_Default_HelloPlugin
extends HTML_QuickForm2_Renderer_Default
implements HTML_QuickForm2_Renderer_Plugin
{
protected $base;
public function setRenderer(HTML_QuickForm2_Renderer $base)
{
$this->base = $base;
}
public function sayHello()
{
$elTpl = $this->base->markRequired(
$this->base->outputError(
$this->base->outputLabel(
$this->base->templatesForClass['html_quickform2_element'],
__CLASS__ . ' says:'
), ''
), false
);
echo str_replace(array('{id}', '{element}'), array('', 'Hello, world!'), $elTpl);
}
}
class HTML_QuickForm2_Renderer_Default_GoodbyePlugin
extends HTML_QuickForm2_Renderer_Default
implements HTML_QuickForm2_Renderer_Plugin
{
protected $base;
public function setRenderer(HTML_QuickForm2_Renderer $base)
{
$this->base = $base;
}
public function sayGoodbye()
{
$elTpl = $this->base->markRequired(
$this->base->outputError(
$this->base->outputLabel(
$this->base->templatesForClass['html_quickform2_element'],
__CLASS__ . ' says:'
), ''
), false
);
echo str_replace(array('{id}', '{element}'), array('', 'Goodbye, world!'), $elTpl);
}
}
HTML_QuickForm2_Renderer::registerPlugin('default', 'HTML_QuickForm2_Renderer_Default_HelloPlugin');
$renderer = HTML_QuickForm2_Renderer::getInstance('default');
// магически добавится к экземпляру класса
HTML_QuickForm2_Renderer::registerPlugin('default', 'HTML_QuickForm2_Renderer_Default_GoodbyePlugin');
$renderer->sayHello();
$renderer->sayGoodbye();
- Экземпляры классов Renderer'ов --- Singleton'ы. Тут вот как раз срач на эту тему идёт. Неудобно тестировать, приходится заводить методы для сбрасывания состояния.
- Наследование плагинов от Renderer'ов.
Расскажите, коллеги, кому приходилось решать похожие задачи, к чему пришли?
Тебе в синглтоне нужны защищенные элементы, которые предназначены для использования другими классами. По нормальному - это называется интерфейс класса. Интерфейс должен быть публичным. Диллема? Нет. Просто подумай, почему интерфейс определенного класса у тебя стремится стать защищенным?