Автор: Евгений Игумнов
Геокад Плюс (Новосибирск)
Домашняя страничка: ejbcorba.euro.ru
Редактор: Денис Щеглов
Версия текста: 1.0 (10.05.2001)
По моим наблюдениям, основная масса программистов пишут CGI-скрипты как попало, и эффективность такой системы их совсем не заботит - "лишь бы работало". И чаще всего программист начинает писать скрипты, даже точно не зная, что у него в конечном итоге получится. Например, если Вы попросите добавить или изменить имеющуюся web-систему, то программист, скорее всего, будет просто несчастлив, так как ему придется перерывать гору исходников, в которых "чёрт ногу сломит". К тому же возникает проблема при распараллеливании работ по созданию сайта между несколькими программистами. К сожалению,раньше и я был в числе таких горе-программистов, но теперь предпочитаю сначала все спроектировать, а уже потом браться за реализацию (как в одиночку, так и командой).
В данной статье я излагаю свой опыт и демонстрирую примеры на технологии Servlet, но описанные здесь идеи легко можно использовать в Perl или PHP.
Существует популярный шаблон проектирования Model - View - Controller. Идея его заключается в следующем. Существует модель (Model) предметной области приложения, например структура базы данных. Существуют способы отображения (View) этой модели для пользователя, например HTML-странички. Существует набор действий (Controller), которые производятся с моделью (например, изменение состояния модели, т.е. содержимого БД, или генерация HTML-кода). Обычно View реагирует на внешние события, такие как метод Post или Get из браузера пользователя, и на каждое такое внешнее воздействие определяет свою команду (Controller). На самом деле MVC немного сложнее, но в нашем случае мы возьмем только выше описанные идеи. Шаблон MVC изображен на рис.1
Рис.1
В нашем случае событие model_was_changed не может произойти, так как события между броузером и CGI-скриптом (Controller) идут только в сторону CGI-скрипта, т.е. CGI-скрипт не может в любой момент времени сообщить пользователю о том, что была изменена модель. Не работающая связь изображена на рис.2.
Рис.2
Самая существенная ошибка, которую совершают программисты - это совмещение кода, изменяющего базу данных и генерация HTML. Предлагаю это разделить и назвать соответствующие программные модули контроллером взаимодействия с БД и котроллером генерации HTML. Ниже показано это разделение.
Рис.3
Первый овал - это код, который меняет состояние таблиц БД, т.е. Controller. Второй генерирует HTML, т.е. опять Controller. Третий овал с черточкой - это представление HTML в браузере на стороне пользователя, т.е. View. А Model является структурой БД, как и говорилось выше.
Теперь перейдем от теории к практике. Разберем маленький пример, используя введенные выше обозначения. Пример изображен на рис.4.
Рис.4
Это пример простой гостевой книги. Гостевая книга представляет собою один скрипт. Ему обычно передаются параметры action, commit и rollback. В action указывается контроллер, который необходимо запустить. Если указан параметр commit, то в случае успешного выполнения контроллера в action, следующим запускается контроллер в commit, а если порождается исключение при выполнении контроллера из action, то запускается контроллер, указанный в rollback. Если action не указан, то выполняется контроллер, определенный по умолчанию. Используя такой подход, Вы можете заранее нарисовать весь сценарий взаимодействия пользователя с web и по полученным кружочкам понять, сколько Вам необходимо написать контроллеров для своей задачи.
Для того, чтобы было легко раздать работу по написанию Ваших контроллеров нескольким программистам, я предлагаю Вам использовать шаблон проектирования под названием Command. Определяем интерфейс MyCommand и наследуем от него все наши команды (контроллеры). Таким образом, Вы налагаете на всех программистов обязанность следовать единому стилю. Шаблон Command изображен на рис.5
Рис.5
Некоторые программисты пишут один большой Servlet и каждый контроллер определяют просто методом, как делает, например, один мой знакомый. У него получается большой единственный файл с кучей методов (каждый метод - это контроллер). А теперь давайте представим, что ему не хватает времени реализовать все контроллеры. Мы даем в помощь 3-х программистов и они все вместе начинают редактировать один его большой Servlet. Думаю, ничего хорошего из этого не выйдет. Давно уже пора понять, что времена "сам все напишу" прошли, что проекты делаются группой разработчиков и каждый разработчик должен писать не как ему хочется, а как нужно, с учетом потребностей всей группы программистов.
Желательно иметь один класс, который бы занимался только тем, что управлял сценарием работы нашего web-сайта. Пример работы такого диспетчера изображен на рис.6.
Рис.6
Как видно из рисунка, Servlet передает управление диспетчеру, а тот, в свою очередь, анализирует содержимое переменной action и вызывает соответствующий контроллер. Если после выполнения контроллера порождается исключение, то он вызывает контроллер, определенный в rollback, а в случае успешного выполнения контроллера из action диспетчер вызывает контроллер, определенный в commit.
На самом деле, выше описанный диспетчер у нас получился не очень хороший. Недостаток на лицо. В случае изменения сценария взаимодействия с Web, необходимо лезть руками в Dispatcher и исправлять код. Я привел такую реализацию Dispatcher'а только для того, чтобы продемонстрировать логику его работы. Кстати, вышеупомянутый знакомый реализует диспетчер именно так.
Существует более изящное решение этой проблемы. Мне его подсказал Антон Патрушев (mcgregor@mail.ru). Необходимо определить текстовой файл, в котором хранятся имена наших контроллеров, которые мы указываем в action, commit и rollback. И каждому имени контроллера указать имя класса, который его реализует. А потом воспользоваться java.lang.ClassLoader. ClassLoader позволяет загружать класс по имени и делать с него объект. Если думать дальше, то в такой конфигурационный файл можно не только зашить соответствия контроллер - класс, но и описание всего сценария. Так вот, механизм отображения контроллер - класс называется Mapper.
Программы желательно писать так, чтобы не повторять один и тот же код, а повторно его использовать. И потом можно будет менять код в одном месте, а не во многих, как чаще всего происходит. Предлагаю применять шаблон Decorator. Диаграмма классов изображена на рис.7.
Рис.7
Идея заключается в том, что у вас есть какой-то визуализируемый элемент и вы хотите его дополнить некоторым набором расширений. Причём в разных ситуациях вы планируете навешивать различные "фенечки". Обычно приводят пример кнопочки, потом к ней добавляют перечеркивающую линию, а затем мигание. И в случае необходимости мигание или линию можно убрать.
Идея заключается в следующем. Определяем интерфейс View с методом, который рисует наш компонент. От интерфейса наследуем наш компонент и его декораторы. И в конструкторах определяем возможность передачи ссылки на вложенный объект, который надо декорировать, так сказать. Получается следующая картина на рис.8.
Рис.8
Сначала объясню, что делает каждый декоратор. Border - просто рисует обрамление вокруг любого элемента. WindowHead - рисует подобие заголовка как у всех приложений Windows с кнопочками свернуть и закрыть. NewsHead - рисует серенькое поле, а в нем пишет название новости. NewsBody - рисует саму новость. Другими словами, заходите на сайт, а там новости оформлены в виде Windows окошек. Некоторые новостные сайты такое практикуют. А теперь вернемся к рис.8. Как видно b1 обрамляет wh1, wh1 содержит b2, b2 обрамляет nh, nh содержит b3, а b3 обрамляет nb. Как работает весь этот механизм изображено на рис.9.
Рис.9
Приведу пример кода:
Border b1= new Border( new WindowsHead( new Border ( new NewsHead ( new Border ( new NewsBody() ) ) ) )); b1.doPost(...);
Шаблон Composite похож на Decorator, можно сказать, что Decorator является частным случаем Composite . У декоратора чистая рекурсивная вложенность, а у композиции присутствует как рекурсия, так и очередь. Даже можно сказать, что в компоненты, построенные по шаблону композиции можно добавлять несколько других подобных компонентов. Диаграмма шаблона Composite изображена на рис.10.
Рис.10
В шаблоне Decorator мы посредством конструктора передавали ссылку на вложенный объект, а здесь мы используем для этого метода AddComponent. Естественно, не все компоненты могут добавлять в себя другие компоненты.
Предположим, что у нас есть набор страничек со стандартным комплектом: баннер сверху, ниже заголовок, слева меню с кнопочками навигации, снизу служебная информация с датой последнего изменения страницы. В виде объектов это можно представить так.
Рис.11
Как видно, у нас есть один компонент (назовем его контейнер), в котором по очереди хранятся другие вложенные компоненты. Механизм работы изображен на рис. 12.
Рис.12
Создаём наш контейнер, потом помещаем туда нужные элементы и заставляем его нарисовать себя, т.е. сформировать HTML-код.
Иногда необходимо иметь гибкий механизм смены способов представления информации. Для этого необходимо разделить способ представления информации и саму информацию. Для этого удобно использовать XML. Давайте представим, что Ваши сервлеты будут формировать не HTML, а XML, который вы жёстко декларируете. И этот XML будете обрабатываться другим скриптом, который содержит в себе правила преобразования вашего XML в HTML-код. И если Вам захочется поменять внешний вид своего сайта, Вам будет достаточно изменить правила отображения XML в HTML. Вы также сможете написать свой апплет, который будет общаться с сервером, получать от него XML и отображать его другим способом в браузере клиента. Другими словами, Вы формируете один и тот же XML, а он может представляться в HTML и в виде апплета. Положительные дополнительные эффекты, которые приносит использование XML, не помешаютсистеме, которая имеет разнородные клиентские приложения.
Я постарался поделиться с Вами своим опытом и не претендую на то, что идеи, изложенные выше, являются самыми-самыми, но думаю, что знание, которое Вы получили при прочтении моей статьи заставят Вас обдумать свои взгляды на процесс разработки и проектирования сценариев взаимодействия с Web-компонентами.