Здесь приведены две моих старых заметки, посвященных теории Веб-приложений, их структуре и вопросам взаимодействий. Примеры на PHP.
В этом посте: общий принцип деления приложения на составляющие, необходимость «ядра», связи между частями Веб-приложения.
На мой взгляд, чтобы объективно смотреть на Веб-приложение, необходимо, для начала, отказаться от использования терминов «MVC», «паттерн», «ООП» и им подобных. Так же стоит закрыть глаза на фундаментальные различия между языками, на одном из которых планируется разрабатывать Веб-приложение. И тогда общая теоретическая модель приложения будет состоять из следующих «слоев»:
Слоем я называю совокупность классов, выполняющих различные задачи, но не выходящие за рамки деятельности текущего слоя.
Например: если класс News_Publisher производит выборку новостей из базы, класс Template_Engine обрабатывает шаблоны, а класс Smarty_Handler, во-первых, унифицирует данные из модуля News_Publisher к формату шаблонизатора Smarty и обрабатывает шаблон, а во-вторых, фактически является реализацией метода класса Template_Engine. Вышесказанное можно описать таким кодом:
Естественно, здесь сразу просматривается паттерн Factory, но я обещал не обращать на них внимания, это было нужно только для примера.
Классы-обработчики по определению не должны формировать никаких Данных, кроме временных, необходимых для выполнения задачи связывания компонентов.
Наверняка вы обратили внимание, что в приложении отсутствует Ядро, на первый взгляд самый первый и обязательный компонент любого Веб-приложения. Признаюсь, когда писалась первая версия этой заметки, ядро было первым слоем приложения. Но потом я исключил его, и сейчас объясню, почему. Дело в том, что как и в любой научной теории, я считаю недопустимыми «компромиссы». Научная теория, претендующая на звание Великой и Всеобъемлющей, должна в обязательном порядке обладать свойством применимости в любых условиях и масштабах. Поэтому никакие приближения не имеют право на существование, т.к. в таком случае теория является неполной. Так же и здесь.
Если подумать, ядро, в стандартном понимании, является смесью обработчика и компонента. Т.е. это не дополнительный подкласс, а просто симбиоз двух существующих, который естественно при желании может быть легко разделен на обработчик и компонент.
Поэтому и предлагается избавиться от понятия «ядра» как такового. Вы можете возразить, что в таком случае некоторый набор функций, которые являются обязательными для любого ядра(например, функция подгрузки библиотек, установления соединения с сервером данных и.т.д.) окажутся в подвешенном состоянии. Вовсе не так — они вполне укладываются в нашу структуру, это обработчики наиболее низкого уровня. А учитывая сложную структуру современных Веб-приложений — такого рода функции давно объединяются в классы, и поэтому ничем не отличаются от стандартных обработчиков.
Таким образом, Ядро это Компонент, который работает с достаточно специфичным видом данных — HTTP-запросами. Но и тут сфера деятельности ядра очень ограничена — необходимо лишь принять запросы, и передать их для обработки другим компонентам. Работу с URL'ами лучше отдать компоненту URLRouter, а с данными запроса — компоненту RequestPeer, например.
Безусловно, со стороны программиста может показаться, что очень неудобно пользоваться связью Компонент — Обработчик — Компонент, и это не тот случай когда надо креститься. Это действительно неудобно, и поэтому придется допустить столь ненавистное, но столь желанное допущение — позволить компонентам знать о друг друге, т.е. позволить им передавать данные удобным способом. Методом статических вызовов, или, если используется структура, представленная в коде ниже, через ссылку на объединяющий объект. Т.е. не заставлять классы искать друг друга с помощью глобальных переменных, списка классов и других нелицеприятных средств. Но все-таки я выступаю против повсеместного применения этого метода из-за лени(а это очень серьезный фактор для разработчика), и постараюсь в следующей заметке попытаться объяснить свою точку зрения.
подобное поведение объектов по отношению к своим родителям можно наблюдать в JavaScript
В этом посте: необходимость однотипных данных, связи между компонентами, суть обработчиков.
В прошлой заметки я пообещал, что обосную свое негативное отношение к типу связи Компонент — Компонент. Вся соль в так называемой «унификации данных». Мы встречаемся с ней каждый день — натуральные языки это по сути метод унификации данных в масштабах одной нации/носителей языка. Таких же примеров можно привести много, а из компьютерной области — протоколы и XML.
Необходимость унификации данных обусловлена, например, установлением равноправия среди приложений или компонентов, работающих с одним источником данных. Если разработчик написал приложение и компонент, которые обмениваются данными собой с помощью симметрично зашифрованного протокола с ключем, зашитым в их исходный код, внедрение стороннего компонента станет проблемой, потому что появится необходимость вручную дешифровать данные от приложения. Аналогичная ситуация и в Веб-приложениях.
Наличие огромного количества различных реализаций для решения одной задачи(те же шаблонизаторы и CRUD библиотеки) налагает условия на структуру приложения. Нельзя заставить владельца CMS использовать именно HTML_Flexy а не привычный ему Smarty только потому, что движок передает данные в шаблон исключительно в виде ссылки на объект, и менять что-либо в движке разработчику естественно лень или не представляется возможным.
И здесь я считаю целесообразным использовать обработчики. В качестве примера:
Естественно, способ выбора обработчика может быть одним из сотен самых различных, зависит от конкретной ситуации.
Заметка 1. Intro, как это ни банально
В этом посте: общий принцип деления приложения на составляющие, необходимость «ядра», связи между частями Веб-приложения.
Сразу к делу
На мой взгляд, чтобы объективно смотреть на Веб-приложение, необходимо, для начала, отказаться от использования терминов «MVC», «паттерн», «ООП» и им подобных. Так же стоит закрыть глаза на фундаментальные различия между языками, на одном из которых планируется разрабатывать Веб-приложение. И тогда общая теоретическая модель приложения будет состоять из следующих «слоев»:
- Обработчики — слой, предоставляющий интерфейсы и протоколы по обмену Данными во втором слое под названием
- Компоненты — внешний слой приложения, непосредственно формирующий Данные и оперирующий ими.
Слоем я называю совокупность классов, выполняющих различные задачи, но не выходящие за рамки деятельности текущего слоя.
Например: если класс News_Publisher производит выборку новостей из базы, класс Template_Engine обрабатывает шаблоны, а класс Smarty_Handler, во-первых, унифицирует данные из модуля News_Publisher к формату шаблонизатора Smarty и обрабатывает шаблон, а во-вторых, фактически является реализацией метода класса Template_Engine. Вышесказанное можно описать таким кодом:
<?php
class Smarty_Handler
{
function view(string $tplfile, $data = null)
{
$smarty = new Smarty ;
$smarty->assign('news', $data) ;
$smarty->display($data) ;
}
}
class Template_Engine
{
private static $handlerName = "" ;
function setHandler(string $classname)
{
self :: $handlerName = $classname ;
}
// похоже на паттерн Abstract Factory, но несколько более топорного вида
function view($tplfile, $data)
{
call_user_func(array(self :: $handlerName, "view"), $data) ;
}
}
class News_Publisher
{
function getNews()
{
// у нас не стоит задача формирования данных из БД или подобных
$news = array(array('title' => 'This', 'content' => 'looks like abstract factory, isnt it? :-)')) ;
return $news ;
}
}
Template_Engine :: setHandler('Smarty_Handler') ;
Template_Engine :: view('myfile', News_Publisher :: getNews()) ;
?>
Естественно, здесь сразу просматривается паттерн Factory, но я обещал не обращать на них внимания, это было нужно только для примера.
Классы-обработчики по определению не должны формировать никаких Данных, кроме временных, необходимых для выполнения задачи связывания компонентов.
Наверняка вы обратили внимание, что в приложении отсутствует Ядро, на первый взгляд самый первый и обязательный компонент любого Веб-приложения. Признаюсь, когда писалась первая версия этой заметки, ядро было первым слоем приложения. Но потом я исключил его, и сейчас объясню, почему. Дело в том, что как и в любой научной теории, я считаю недопустимыми «компромиссы». Научная теория, претендующая на звание Великой и Всеобъемлющей, должна в обязательном порядке обладать свойством применимости в любых условиях и масштабах. Поэтому никакие приближения не имеют право на существование, т.к. в таком случае теория является неполной. Так же и здесь.
Если подумать, ядро, в стандартном понимании, является смесью обработчика и компонента. Т.е. это не дополнительный подкласс, а просто симбиоз двух существующих, который естественно при желании может быть легко разделен на обработчик и компонент.
Поэтому и предлагается избавиться от понятия «ядра» как такового. Вы можете возразить, что в таком случае некоторый набор функций, которые являются обязательными для любого ядра(например, функция подгрузки библиотек, установления соединения с сервером данных и.т.д.) окажутся в подвешенном состоянии. Вовсе не так — они вполне укладываются в нашу структуру, это обработчики наиболее низкого уровня. А учитывая сложную структуру современных Веб-приложений — такого рода функции давно объединяются в классы, и поэтому ничем не отличаются от стандартных обработчиков.
Таким образом, Ядро это Компонент, который работает с достаточно специфичным видом данных — HTTP-запросами. Но и тут сфера деятельности ядра очень ограничена — необходимо лишь принять запросы, и передать их для обработки другим компонентам. Работу с URL'ами лучше отдать компоненту URLRouter, а с данными запроса — компоненту RequestPeer, например.
Безусловно, со стороны программиста может показаться, что очень неудобно пользоваться связью Компонент — Обработчик — Компонент, и это не тот случай когда надо креститься. Это действительно неудобно, и поэтому придется допустить столь ненавистное, но столь желанное допущение — позволить компонентам знать о друг друге, т.е. позволить им передавать данные удобным способом. Методом статических вызовов, или, если используется структура, представленная в коде ниже, через ссылку на объединяющий объект. Т.е. не заставлять классы искать друг друга с помощью глобальных переменных, списка классов и других нелицеприятных средств. Но все-таки я выступаю против повсеместного применения этого метода из-за лени(а это очень серьезный фактор для разработчика), и постараюсь в следующей заметке попытаться объяснить свою точку зрения.
подобное поведение объектов по отношению к своим родителям можно наблюдать в JavaScript
<?php
class Main
{
}
class Counter
{
var $main ;
function __construct(Main $obj)
{
// PHP6 вероятно искоренит подобную запись
$this->main = & $obj ;
}
function getRange()
{
return range(5, 20) ;
}
}
class Joiner
{
var $main ;
function __construct(Main $obj)
{
$this->main = & $obj ;
}
function showLine()
{
$range = $this->main->counter->getRange() ;
echo join(", ", $range) ;
}
}
$main = new Main ;
$main->counter = new Counter($main) ;
$main->joiner = new Joiner($main) ;
$main->joiner->showLine() ;
?>
Заметка 2. Handlers и унификация данных
В этом посте: необходимость однотипных данных, связи между компонентами, суть обработчиков.
В прошлой заметки я пообещал, что обосную свое негативное отношение к типу связи Компонент — Компонент. Вся соль в так называемой «унификации данных». Мы встречаемся с ней каждый день — натуральные языки это по сути метод унификации данных в масштабах одной нации/носителей языка. Таких же примеров можно привести много, а из компьютерной области — протоколы и XML.
Необходимость унификации данных обусловлена, например, установлением равноправия среди приложений или компонентов, работающих с одним источником данных. Если разработчик написал приложение и компонент, которые обмениваются данными собой с помощью симметрично зашифрованного протокола с ключем, зашитым в их исходный код, внедрение стороннего компонента станет проблемой, потому что появится необходимость вручную дешифровать данные от приложения. Аналогичная ситуация и в Веб-приложениях.
Наличие огромного количества различных реализаций для решения одной задачи(те же шаблонизаторы и CRUD библиотеки) налагает условия на структуру приложения. Нельзя заставить владельца CMS использовать именно HTML_Flexy а не привычный ему Smarty только потому, что движок передает данные в шаблон исключительно в виде ссылки на объект, и менять что-либо в движке разработчику естественно лень или не представляется возможным.
И здесь я считаю целесообразным использовать обработчики. В качестве примера:
<?php
class Fruit
{
var $state ;
var $name ;
function __construct($name, $state)
{
$this->name = $name ;
$this->state = $state ;
}
}
class Bucket
{
var $accept = "objects" ;
function flush($fruits)
{
foreach ($fruits as $item)
{
echo $item->name . " is " . $item->state . "<br>" ;
}
}
}
class Fruits_Objects_Handler
{
function prepare($fruits)
{
$prepared_fruits = array() ;
foreach ($fruits as $k => $v)
$prepared_fruits[] = new Fruit($k, $v) ;
return $prepared_fruits ;
}
}
// здесь компонент приложения возвращает данные в удобном ему виде(или в единственно возможном, или наиболее полном)
$fruits = array('apple' => 'fresh', 'banana' => 'rotten', 'pear' => 'fresh', 'orange' => 'rotten') ;
$bucket = new Bucket ;
// здесь устанавливается обработчик, на основании используемого компонентом типа данных
if ($bucket->accept == "objects")
$fruits = call_user_func(array("Fruits_Objects_Handler", "prepare"), $fruits) ;
// данные преобразуются обработчиком
// здесь другой компонент выполняет операции с уже отформатированными для него данными
$bucket->flush($fruits) ;
?>
Естественно, способ выбора обработчика может быть одним из сотен самых различных, зависит от конкретной ситуации.