Pull to refresh

Теория Веб-приложений: 2 старых заметки

Reading time 5 min
Views 869
Здесь приведены две моих старых заметки, посвященных теории Веб-приложений, их структуре и вопросам взаимодействий. Примеры на PHP.


Заметка 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) ;
?>


Естественно, способ выбора обработчика может быть одним из сотен самых различных, зависит от конкретной ситуации.
Tags:
Hubs:
+1
Comments 3
Comments Comments 3

Articles