Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
107
Добавлен:
02.05.2014
Размер:
6.34 Mб
Скачать

Глава 3. Управление кодом Ajax

115

Рис. 3.3. Использование образа разработки Command для реализации универсального стека отмены действий в текстовом процессоре. Все действия пользователя представляются как объекты Command и могут быть легко отменены

Оформление всех действий пользователя в виде объекта Command может показаться сложным решением, однако затрата усилий окупается. Если все действия пользователя объединены в объекте Command, мы можем без труда связать с ними другие стандартные функции. При обсуждении образа разработки Command в качестве примера предоставляемых им возможностей чаще всего рассматривается добавление метода undo (), предполагающего отмену результатов действий в пределах всего приложения. При использовании образа разработки Command объекты, инкапсулирующие действия пользователя, записываются в стек, а при активизации кнопки, предназначенной для отмены действий, извлекаются из стека (рис. 3.3).

Каждый новый объект помещается в вершину стека, в результате появляется возможность пошаговой отмены действий. Процесс создания документа представляет собой не что иное, как последовательность действий. В какой-то момент пользователь может выделить весь текст документа и случайно нажать клавишу <Delete>. При вызове функции undo() извлекается верхний элемент стека и вызывается его метод undo (), который возвращает удаленный текст. Следующий вызов данной функции отменит выбор текста и т.д.

116 Часть I. Новый взгляд на Web-приложение

Очевидно, что использование объекта Command для создания стека отмены действий требует от разработчика дополнительных усилий, направленных на то, чтобы после выполнения команды и последующей ее отмены система вернулась в первоначальное состояние. Однако возможность отмены предыдущих действий выгодно отличает приложение от тех программ, в которых подобная функция не реализована. В особенности это валено для тех приложений, которые используются длительное время. Как было сказано в главе 1,1 создание Web-приложений, предоставляющих пользователю удобный интерфейс, — это как раз та область, на которую ориентировались разработчики Ajax.

Объекты Command могут быть полезны тогда, когда необходимо передавать информацию между отдельными подсистемами приложения. Приме-' ром "границы", разделяющей подсистемы, может служить сетевое соединение. К рассмотрению образа разработки Command мы вернемся в главе 5,! которая будет посвящена взаимодействию клиента и сервера.

3.2.4. Обеспечение единственной ссылки на ресурс: образ разработки Singleton

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

Поддержка торговых сделок

Предположим, что наше Ajax-приложение работает с биржевой информацией: позволяет пользователю делать реальные покупки и продажи, выполняет необходимые вычисления, а также обеспечивает режим имитации биржевой деятельности. Мы определили три режима работы приложения и дали им название цветов, которыми подаются известные всем сигналы светофора. В режиме реальной работы (зеленом режиме) пользователь может покупать и продавать ценные бумаги и выполнять вычисления на основе имеющихся данных. Когда биржа закрыта, доступен только режим анализа (красный режим), в котором доступны любые действия, кроме реальной покупки и продажи. В режиме имитации (желтый режим) эмулируются все действия, доступные в зеленом режиме, но реальное взаимодействие с рынком не происходит.

Наша клиентская программа поддерживает режимы с помощью объекта JavaScript.

var MODE_RED=1; var MODE_AMBER=2; var MODE_GREEN=2;

function TradingMode(){ this.mode=MODE_RED;

}

Мы можем запрашивать и устанавливать режим для данного объекта, причем не исключено, что это будет сделано в разных местах программы. Мы также могли бы определить функции getMode () и setMode () и реализовать

Глава 3. Управление кодом Ajax 117

Рис. 3.4. В рассматриваемом Ajax-приложении средства покупки-продажи и функции анализа определяют, какие данные надо использовать: реальные или имитированные. Для этого они обращаются к объекту T r a d i n g M o d e . В желтом режиме происходит обращение к серверу-имитатору, а в зеленом режиме — к реальному биржевому серверу. Если в системе имеется несколько объектов T r a d i n g M o d e , их состояние может стать несогласованным,

врезультате система будет находиться в неопределенном режиме

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

Предположим, что пользователю доступны средства, позволяющие покупать и продавать ценные бумаги, а также вычислять выигрыш или потери от сделки, прежде чем совершать ее. В зависимости от режима взаимодействие, связанное с покупкой и продажей, может осуществляться с одной из двух служб: имитатором в желтом режиме и сервером брокера в зеленом режиме. В красном режиме средства взаимодействия заблокированы. Анализ производится на основе текущих и предыдущих цен. Источником данных являются имитатор в желтом режиме и биржевые сводки в зеленом режиме. Для того чтобы определить, откуда следует получать данные и с какой службой должно производиться взаимодействие при покупке и продаже, надо обратиться к объекту TradingMode (рис. 3.4).

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

118 Часть I. Новый взгляд на Web-приложение

потеряет работу.

Объект, существование которого допускается в единственном экземпляре, называется единичным объектом (singleton). Сначала мы обсудим, как единичные объекты поддерживаются в объектно-ориентированном языке, а затем рассмотрим особенности работы с ними JavaScript-программ.

Единичные объекты в Java

В Java и других подобных языках для реализации единичных объектов применяется следующий подход: конструктор скрывается, а вместо него предоставляется get-метод (листинг 3.3).

Листинг 3.3. Единичный объект TradingMode в языке Java

public class TradingMode{

private static TradingMode instance=null; public int mode;

private TradingMode(){ mode=MODE_RED;

}

public

static TradingMode getlnstance(){

if (instance==null){

instance=new TradingMode();

J

 

return

instance;

}

 

public

void setMode(int mode){

}

 

i

 

}

_

В языке Java для определения поведения единичных объектов используются модификаторы доступа private и public. Приведенный ниже код не будет компилироваться, поскольку конструктор недоступен из других классов.

new TradingMode().setMode(MODE_AMBER);

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

TradingMode.getlnstance().setMode(MODE_AMBER);

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

Единичные объекты в JavaScript

В JavaScript отсутствуют модификаторы доступа, поэтому, для того, чтобы "скрыть" конструктор, мы не будем создавать его. Язык JavaScript основан на прототипах, а конструкторы представляют собой объекты Function (подробно об этом речь пойдет в приложении Б). Мы можем создать объект TradingMode обычным способом.

Глава 3. Управление кодом Ajax 119

function TradingMode(){ this.mode=MODE_RED;

}

TradingMode.prototype.setMode=function(){

}

В качестве псевдоединичного объекта мы предоставим глобальную переменную.

TradingMode.instance=new TradingMode();

Однако такой подход не предотвращает вызов конструктора. С другой стороны, мы можем создать весь объект вручную, не прибегая к прототипу.

var TradingMode=new Object(); TradingMode.mode=MODE_RED; TradingMode.setMode=function(){

}

Тот же результат можно получить с помощью более краткой записи.

var TradingMode={ mode:MODE_RED, setMode:function() {

}

};

Оба приведенных выше примера генерируют идентичные объекты. Первый способ более привычен программистам, использующим Java или С# . Второй способ мы показали здесь потому, что он часто используется в библиотеке Prototype и в базовых наборах средств, созданных на ее основе.

Данное решение работает в пределах одного контекста. Если сценарий будет загружен в отдельный элемент iFrame, он создаст собственную копию единичного объекта. Избежать такого эффекта можно, указав, что единичный объект доступен из документа верхнего уровня (в JavaScript свойство top всегда ссылается на текущий документ). Данный подход иллюстрируется кодом, представленным в листинге 3.4.

Листинг 3.4. Единичный объект TradingMode в JavaScript Function getTradingMode(){

if (!top.TradingMode){ top.TradingMode=new Object(); top.TradingMode.mode=MODE_RED; top.TradingMode.setMode=function(){

}

J

return

top.TradingMode;

}

_

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

120 Часть I. Новый взгляд на Web-приложение

вам надо обратиться к top.opener. Из-за ограниченного объема книги мы предлагаем читателям сделать это самостоятельно.)

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

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

Впроцессе обсуждения мы рассмотрели несколько шаблонов разработки.

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

иобеспечить для него дополнительную степень гибкости.

3.3. "Модель-представление-контроллер "

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

Архитектура "модель-представление-контроллер" (Model-View-Control- ler — MVC) позволяет выделить фрагменты кода, отвечающие за взаимодействие с пользователем, и отделить их от тех частей программы, которые предназначены для выполнения сложных расчетов или реализации бизнеслогики. В этой главе мы покажем, как применить ее к разработке серверных компонентов, управляющих данными для Ajax-приложения. В главе 4 мы пойдем еще дальше и применим данную архитектуру для клиентской JavaScript-программы.

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

Представление (его иногда также называют просмотром) — это программные средства, которые представляют информацию пользователю: реализуют формы ввода, изображения, текст, компоненты. Представление не обязательно должно работать с графикой. В программе, предоставляющей голосовой интерфейс, представление может быть реализовано на базе синтезатора речи.

Глава 3. Управление кодом Ajax

121

Рис. 3.5. Основные компоненты архитектуры "модель—представление-контроллер". Представление и модель не могут обращаться друг к другу — они взаимодействуют только через контроллер. Контроллер рассматривается как промежуточный уровень между моделью и представлением. Благодаря такому подходу разграничиваются функции различных частей программы, обеспечивается дополнительная степень гибкости и упрощается сопровождение приложения

Основное правило архитектуры MVC гласит, что представление и модель не должны иметь доступа друг к другу. На первый взгляд такая программа может показаться неработоспособной, но здесь на помощь приходит контроллер. Когда пользователь нажимает клавишу или заполняет форму, представление сообщает об этом контроллеру. Контроллер выполняет действия с моделью и решает, должно ли измениться представление в соответствии с новым состоянием модели. Если изменения необходимы, контроллер сообщает о них представлению (рис. 3.5).

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

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

Аналогично разработчики могут модифицировать представление, не опасаясь повредить модель. Понятно, что подобная ситуация сохраняется лишь До тех пор, пока будет соблюдаться соглашение об использовании интерфей-

122 Часть i. Новый взгляд на Web-приложение

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

У разработчиков классических Web-приложений уже накоплен опыт создания их в рамках архитектуры MVC. Эта архитектура помогает управлять последовательностью статических документов, составляющих интерфейс приложения. Когда приложение Ajax запускается и запрашивает информацию с сервера, используется тот лее механизм доставки данных, что и в классическом приложении. Таким образом, серверная часть Ajax-приложения может создаваться в рамках архитектуры "модель-представление-контроллер". Поскольку вопросы использования этой архитектуры для создания серверных программ уже хорошо отработаны и понять основные принципы несложно, мы начнем обсуждение MVC с серверной части, а затем перейдем к применениям, специфическим для Ajax.

Если вам еще не знакомы базовые средства разработки Web-программ, в следующем разделе вы найдете общие сведения о них и узнаете, как повысить масштабируемость и надежность Ajax-приложения. Если вы имеете опыт работы с инструментами Web-программирования, например, с процессорами шаблонов и ORM (Object-Relational Mapping), либо с такими средствами как Struts, Spring или Tapestry, то, наверное, уже в основном владеете сведениями, которые будут излагаться здесь. В этом случае мы советуем вам сразу перейти к главе 4, в которой будут обсуждаться другие способы использования этих инструментов.

3.4. Применение МУС для серверных программ

Архитектура "модель-представление-контроллер" применима к Web-прило- жениям, даже к их классическому варианту, который мы столь интенсивно критикуем в этой книге. Сама суть Web-приложения предписывает разделение представления и модели, поскольку поддерживающие их программы выполняются на различных машинах. Значит ли это, что любое Webприложение автоматически попадает в категорию MVC и можно ли создать такую Web-программу, в которой средства представления и модели были объединены?

К сожалению, это возможно. Более того, сделать это достаточно просто, и многие разработчики, в том числе и авторы данной книги, попадали в эту ловушку.

Большинство сторонников применения архитектуры "модель-представле- ние-контроллер" считают представлением не средства воспроизведения HTML-документа, а сам документ и генерирующий его код. Если применить такую точку зрения к приложению Ajax, в котором данные передаются JavaScript-клиенту, окажется, что представление — это XML-документ, передаваемый клиенту в составе HTTP-ответа. Для того чтобы отделить документ от бизнес-логики, требуются определенные навыки.

Глава 3. Управление кодом Ajax

123

Рис; 3.6. Основные компоненты программы, используемой для генерации списка продукции интерактивного магазина. Список представляется в формате XML. При генерации представления мы извлекаем данные из базы, заполняем ими .структуры, соответствующие изделиям, а затем передаем информацию клиенту в виде потока XML-данных

3.4.1 Серверная программа Ajax, созданная без применения образов разработки

В качестве примера, иллюстрирующего обсуждаемый материал, создадим серверную программу для приложения Ajax. Основы создания клиентского кода Ajax мы уже рассматривали в главе 2 и в разделе 3.1.4, кроме того, мы вернемся к этому вопросу в главе 4. Сейчас же мы сосредоточим внимание на том, что происходит на Web-сервере. Начнем с программы, написанной самым простым способом, постепенно реструктуризируем ее в соответствии с архитектурой MVC и рассмотрим, выиграет ли от этого приложение. Сначала определим назначение самого приложения.

Вбазе данных хранится информация об одежде, имеющейся в наличии

вмагазине. Нам надо организовать обращение к базе и представить список товаров пользователю, причем представляемые данные должны содержать изображение, заголовок, краткое описание и цену. Если существуют различные варианты одного и того же изделия, отличающиеся, например, цветом или размером, это также надо учесть. На рис. 3.6 показаны основные компоненты системы, а именно: база данных, структура данных, представляющая конкретный продукт, и XML-доку мент, передаваемый Ajax-клиенту с перечнем всех продуктов (документ должен соответствовать запросу).

Предположим, что пользователь только что приступил к работе и ему предлагается выбрать категорию мужской, женской или детской одежды. Каждое изделие принадлежит одной из этих категорий; соответствующая информация хранится в базе данных, в столбце Category таблицы Garments. SQL-запрос, предназначенный для получения данных категории Menswear, имеет следующий вид:

SELECT * FROM garments WHERE CATEGORY = 'Menswear';

Нам надо получить результаты этого запроса, а затем передать их Ajaxприложению в формате XML. Рассмотрим, как можно сделать это.

124 Часть I. Новый взгляд на Web-приложение

Генерация XML-данных для передачи клиенту

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

Листинг 3.5. Код, генерирующий XML-данные на основе запроса к базе

<?php

//Информация для клиента о том, что ему пересылается

//XML-информация

header("Content-type: application/xml");

echo "<?xml version=\"l.0\" encoding=\"UTF-8\" ?>\n"; $db=mysql_connect("my_db_server","mysql_user") ;

//Получение данных из базы mysql_select_db("mydb",$db);

$sql="SELECT id,title,description,price,colors,sizes"

."FROM garments WHERE category=\"{$cat}\""; $result=mysql_query($sql,$db);

echo "<garments>\n";

//Просмотр набора результатов

while ($myrow = raysql_fetch_row($result)) { printf("<garment id=\"%s\" title=\"%s\">\n"

."<description>%s</description>\n<price>%s</price>\n", $myrow["id"],

$myrow["title"], $myrow["description"], $myrow["price"]);

if (!is_null($myrow["colors"])){

echo "<colors>{$myrow['colors']}</colors>\n";

}

if (!is_null($myrow["sizes"])){

echo "<sizes>{$myrow['sizes']}</sizes>\n";

}

echo "</garment>\n";

}

echo "</garments>\n"; ?>

Код РНР в листинге 3.5 генерирует XML-документ. Если условиям запроса удовлетворяют только два изделия, этот документ будет выглядеть так, как показано в листинге 3.6. Отступы были добавлены лишь для того, чтобы код стал более удобным для восприятия. Язык XML очень часто используется для обмена данными между клиентом и сервером, кроме того, в главе 2 мы уже говорили об обработке XML-документа, полученного с сервера с помощью объекта XMLHttpRequest. В главе 5 мы рассмотрим другие варианты организации обмена.

Листинг 3.6. Пример данных, сгенерированных кодом из листинга 3.5

<garments>

<garment id="SCK001" title="Golfers' Socks"> <description>Garish diamond patterned socks. Real wool.