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

    Здесь приведены две моих старых заметки, посвященных теории Веб-приложений, их структуре и вопросам взаимодействий. Примеры на 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) ;
    ?>
    


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

    Комментарии 3

      +2
      $this->main = &$obj;

      Вам не нужно ждать шестой версии, объекты присваиваются и передаются как параметры по ссылкам уже с пятой версии. Если я, конечно, правильно понимаю и это присваение не имеет какого-то более сакраментального смысла.
        0
        Здесь это больше для наглядности, код не имеет практического смысла, а «жесткая» ссылка в записи выглядит показательней.
        +2
        Желание изначально «абстрагироваться» от общепринятой терминологии значительно усложняет процесс восприятия ваших мыслей и доводов. Только на одном обмене и формировании данных логика приложений не заканчивается. Есть очень много здравых идей, но, мне кажется, вы сами себя запутываете пытаясь изобретать новые названия и понятия.
        короче если быть кратким — бросьте вы эти попытки изобрести велосипед, лучше еще раз (ну или впервые) прочтите Фаулера и Буча. Освежает мозги и дает пищу для размышлений.

        Это что касается теории ).

        Ну а по поводу практики есть пару (мелких) замечаний/придирок:
        1. неявный вызов методов у классов усложняет контроль над приложением. call_user_func(array(string $classname, 'view')) в такой операции никак нельзя гарантировать что интерфейс класса $classname имеет метод view. в данном случае, на мой взгляд, проще передавать экземпляр класса Smarty_Handler, а не строку 'Smarty_Handler'
        2. var $main; и $this->main = & $obj; — это не красиво). (no comments)
        3. выражения типа $this->main->counter->getRange(); существенно подрывают принцыпи слабого связывания. класс, содержащий эту строку, должен знать не только интерфейс объекта main, но и интерфейс counter, на которого он даже в явном виде не ссылается. в дальнейшем подобные проектные решения существенно усложняют рефакторинг и тестирование. Не говоря уже о public properties, которые вобще не являются интерфейсом.
        4. call_user_func(array(«Fruits_Objects_Handler», «prepare»), $fruits) полностью равносильно Fruits_Objects_Handler:: prepare($fruits); только читается гораздо хуже. собственно зачем тогда все это?
        5 и последнее — примите назад «MVC», «паттерн», «ООП»; используйте типовые реализации Factory и (что немаловажно) не ебите мозг :).

        ps: Крэг Ларман вам в помощь ).

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое