Статьи

Шаблоны для гибкой обработки представлений, часть 2. Использование декораторов

В течение многих лет мы были так заняты обсуждением достоинств и недостатков тандема модель / контроллер, что упустили из виду роль представлений, унижая их до уровня неуклюжих шаблонов HTML. Хотя происхождение MVC не коренится в парадигме ООП как таковой , большинство реализаций, доступных в настоящее время в дикой природе, полагаются на использование классов. Короче говоря, это означает, что если наша собственная высокомерная природа заставляет нас верить в то, что уравнение «view = HTML template» является действительным, мы не сможем увидеть преимущества представления о представлениях как о богатых объектах, несущих состояние и поведение, которыми можно манипулировать с помощью логика нескольких шаблонов проектирования.

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

Действительно, обработка отдельных и составных представлений через поводья унифицированного API обеспечивает большую гибкость, так как позволяет нам отображать древовидные структуры (в большинстве случаев это фрагменты HTML, но это может быть и то, что браузер может переварить). без необходимости чрезмерного загрязнения клиентского кода вонючими условностями — зловещий признак нарушения принципа Open / Closed .

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

Функциональность конвейерной обработки во время выполнения — вывод декораторов на стол

Вот быстрый тест, который вы можете примерить на своих коллег по работе: застать их врасплох, спросив их, где можно использовать декораторы в PHP. Они, вероятно, отреагируют, сказав что-то вроде «ну, Zend Framework реализует декораторы форм, и PHPUnit также использует их… и Symfony где-то их отбрасывал, но я не совсем уверен, где на самом деле». На самом деле, это, вероятно, то, что многие из нас будут бормотать в таких ситуациях, так как декораторы не являются существами с плодотворным существованием в мире PHP.

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

Это звучит как неясная концепция? Давайте попробуем пример, где HTML-элемент моделируется как визуализируемая структура, реализующая следующий контракт:

<?php namespace LibraryHtml; interface HtmlElementInterface { public function getText(); public function render(); } 
 <?php namespace LibraryHtml; class Span implements HtmlElementInterface { protected $text; public function __construct($text) { if (!is_string($text) || empty($text)) { throw new InvalidArgumentException( "The text of the element must be a non-empty string."); } $this->text = $text; } public function getText() { return $this->text; } public function render() { return "<span>" . $this->text . "</span>"; } } 

Создание класса, который выводит один элемент <span>, не добавит новую метку в пояс разработчика, но полезно показать, как использовать преимущества декораторов.

Допустим, нам нужно добавить некоторые дополнительные функции к объекту, который отображает элемент, или, в более широком диапазоне, к любому другому HtmlElementInterface интерфейса HtmlElementInterface . Чтобы сделать это, мы должны сначала удалить общую реализацию внутри границ абстрактного декоратора, как этот:

 <?php namespace LibraryHtml; abstract class AbstractHtmlDecorator implements HtmlElementInterface { protected $element; public function __construct(HtmlElementInterface $element) { $this->element = $element; } public function getText() { return $this->element->getText(); } public function render() { return $this->element->render(); } } 

С классом AbstractHtmlDecorator использующим делегирование, легко выделить несколько конкретных декораторов, которые выполняют некоторые дополнительные задачи для внедренного HTML-элемента за кулисами. Кроме того, нижеприведенные схемы используют эту концепцию для обертывания разметки элемента внутри некоторых тегов <p>, <div> и <section>:

 <?php namespace LibraryHtml; class DivDecorator extends AbstractHtmlDecorator { public function render() { return "<div>" . $this->element->render() . "</div>"; } } 
 <?php namespace LibraryHtml; class ParagraphDecorator extends AbstractHtmlDecorator { public function render() { return "<p>" . $this->element->render() . "</p>"; } } 
 <?php namespace LibraryHtml; class SectionDecorator extends AbstractHtmlDecorator { public function render() { return "<section>" . $this->element->render() . "</section>"; } } 

Следует признать, что роль каждого декоратора довольно банальна. Еще посмотрим, как они работают:

 <?php use LibraryLoaderAutoloader, LibraryHtmlSpan, LibraryHtmlParagraphDecorator, LibraryHtmlDivDecorator, LibraryHtmlSectionDecorator; require_once __DIR__ . "/Library/Loader/Autoloader.php"; $autoloader = new Autoloader(); $autoloader->register(); $div = new DivDecorator( new ParagraphDecorator(new Span("Welcome to SitePoint!")) ); echo $div->render(); $section = new SectionDecorator( new DivDecorator( new ParagraphDecorator(new Span("Welcome to SitePoint!")) ) ); echo $section->render(); 

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

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

Использование декораторов более прагматичным способом — настройка модуля расширяемого представления

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

Чтобы заставить модуль работать, я собираюсь использовать тот же класс View я написал в первой части, в котором реализован отдельный интерфейс ViewInterface . Поскольку этот компонент уже закодирован, мы можем сосредоточиться на нескольких декораторах, которые будут отвечать за отображение пользовательских верхних и нижних колонтитулов и макетов. Вот иерархия декораторов для выполнения этих задач:

 <?php namespace LibraryView; abstract class AbstractViewDecorator implements ViewInterface { const DEFAULT_TEMPLATE = "default.php"; protected $template = self::DEFAULT_TEMPLATE; protected $view; public function __construct(ViewInterface $view) { $this->view = $view; } public function render() { return $this->view->render(); } protected function renderTemplate(array $data = array()) { extract($data); ob_start(); include $this->template; return ob_get_clean(); } } 
 <?php namespace LibraryView; class OuterViewDecorator extends AbstractViewDecorator { const DEFAULT_TEMPLATE = "layout.php"; public function render() { $data["innerview"] = $this->view->render(); return $this->renderTemplate($data); } } 
 <?php namespace LibraryView; class HeaderViewDecorator extends AbstractViewDecorator { const DEFAULT_TEMPLATE = "header.php"; public function render() { return $this->renderTemplate() . $this->view->render(); } } 
 <?php namespace LibraryView; class FooterViewDecorator extends AbstractViewDecorator { const DEFAULT_TEMPLATE = "footer.php"; public function render() { return $this->view->render() . $this->renderTemplate(); } } 

Каждый декоратор представляет собой простую оболочку для объектов с внедренным представлением, которые, в свою очередь, переопределяют базовый метод render() для добавления некоторых определенных разделов веб-страниц в шаблоны объектов. Процесс рендеринга должен быть еще проще понять, если я приведу несколько тестируемых примеров. Итак, предположим, что мы определили несколько скелетных шаблонов, называемых partial.php и layout.php , которые выглядят следующим образом:

 <h2><?php echo $this->heading;?></h2> <p><?php echo $this->content;?></p> 
 <!doctype html> <html> <head> <meta charset="utf-8"> <title>My fancy web page</title> </head> <body> <header> <h1>A sample header</h1> </header> <section> <?php echo $innerview;?> </section> <footer> <p>A sample footer</p> </footer> </body> </html> 

Теперь, если нам когда-нибудь понадобится встроить частичку прямо во внешний макет, процесс будет сокращен до кодирования следующего фрагмента:

 <?php $view = new View("partial"); $view->heading = "This is the sample heading line"; $view->content = "This is the sample content"; $page = new OuterViewDecorator($view); echo $page->render(); 

Это был легкий ветерок. Но мы еще не закончили; есть еще несколько изящных вещей, с которыми можно поиграть. Как насчет рендеринга аналогичного документа HTML5, на этот раз путем присоединения верхнего и нижнего колонтитула во время выполнения? Итак, давайте определим шаблоны, связанные с соответствующими декораторами, а именно header.php и footer.php :

 <!doctype html> <html> <head> <meta charset="utf-8"> <title>My fancy web page</title> </head> <body> <header> <h1>A sample header</h1> </header> 
 <footer> <p>A sample footer</p> </footer> </body> </html> 

Имея шаблоны на месте, мы можем теперь породить объект представления, содержащий частичное, и затем удалить декораторы в желаемой последовательности, как показано ниже:

 <?php $view = new View("partial"); $view->heading = "This is the sample heading line"; $view->content = "This is the sample content"; $page = new FooterViewDecorator(new HeaderViewDecorator($view)); echo $page->render(); 

Хотя я должен признать, что процесс рендеринга правильно сформированного HTML-документа в этом случае немного более громоздок и утомителен, чем просто обработка одного шаблона с разбросанным по нему PHP-кодом, в двух словах он показывает, как использовать функциональность, предоставляемая несколькими упрощенными декораторами при реализации настраиваемого модуля представления. Кроме того, учитывая присущую модулю гибкость, довольно легко сделать его немного богаче, вставив внутрь его некоторые дополнительные декораторы, другие, чем только что реализованные.

Если вы чувствуете смелость и хотите справиться с этой задачей, у вас будет возможность отточить свои навыки кодирования бесплатно.

Последние мысли

Очень немногие не согласятся с тем, что шаблоны Composite и Decorator являются одними из самых вопиющих примеров мантры «Favor Composition over Inheritance» в действии. Несмотря на это, они все еще не учитывают парадигмы в PHP, ограниченные темными углами лишь нескольких популярных фреймворков.

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

Изображение через Fotolia