Журнал LinuxFormat - перейти на главную

LXF92:Java EE

Материал из Linuxformat
Перейти к: навигация, поиск
Java Enterprise Edition Учимся писать клиент-серверные приложения на Java

Содержание

MVC в J2EE

ЧАСТЬ 4 Название статьи выглядит как китайская грамота? Не волнуйтесь – Антон Черноусов, вооруживщийся книгой Гамма, проведет экспресс-курс по паттернам объектно-ориентированного проектирования.

В предыдущей статье мы говорили о сессионных и контекстных объектах, о том, как создавать и использовать фильтры, и рассмотрели простой пример авторизации пользователя. Сегодня мы рассмотрим паттерн MVC и его вариацию для создания web-приложений – Model2.

Скачать исходный код примера

Волшебное слово «паттерн»

Паттерн Model-View-Controller (MVC) появился очень давно и достаточно активно использовался в Smalltalk-80. Он предназначен для построения интерфейсов пользователя [1].

Прежде чем окунуться в дальнейшее изучение Java, давайте более подробно остановимся на слове «паттерн». Паттерн – это стандартный способ эффективного решения той или иной задачи в некотором контексте, но для разработчиков программного обеспечения это слово приобрело особое значение. К программному обеспечению этот термин был впервые применен Кристофером Александром [2], который разработал структуру для документирования паттернов и коллекцию паттернов, которые были названы «языками паттернов».

При описании любого паттерна обычно используется следующая структура:

  • название и классификация паттерна;
  • назначение;
  • мотивация для использования;
  • рекомендации по применению паттерна;
  • структура паттерна;
  • участники или элементы паттерна;
  • отношения между элементами;
  • результаты применения;
  • описание идеи реализации паттерна;
  • пример кода (по возможности);
  • родственные паттерны.

Представленная выше структура описания паттерна применена в книге Э. Гамма, ставшей классическим справочником по паттернам [3].

Фактически, паттерн является средством описания общих решений распространенных задач, то есть это – абстрактное решение технических задач, с которыми приходится сталкиваться программистам [4].

Если сказать еще более простым языком, то паттерн – это стандартное решение стандартной проблемы. Моему коллеге очень нравится следующий пример. Задача: построить кирпичную стену, через которую можно видеть; решение: строить эту стену с окном. В связи с тем, что количество стандартных задач очень велико, паттернов тоже существует достаточно много.

Что такое MVC?

MVC – паттерн, представляющий объектно-ориентированный метод для разделения логики представления, бизнес-логики и модели данных. MVC – не просто удачное решение, а общепринятый образец для построения современных приложений. Иногда паттерн MVC называют «архитектурой». На рис. 1 представлена схема взаимодействия компонентов MVC.

Рис. 1

Рис. 1. Архитектура MVC.

Как вы можете видеть на этом рисунке, основными компонентами являются Модель (Model), Вид (View) и Контроллер (Controller). Компонент Модель представляет собой совокупную модель данных предметной области, реализованной в приложении, например, объект, содержащий в себе информацию о счете пользователя. Вид – это компонент, отвечающий за отображение Модели и реализацию пользовательского интерфейса. Вся логика по обработке действий пользователя сосредоточена в Контроллере.

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

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

Что не так с гибкостью в приложениях, построенных по другим схемам? В книге Брюса Тейта [5] уделено достаточно много внимания этой проблеме. Вся загвоздка в том, что достаточно часто модель данных и ее представление пользователю сильно переплетены между собой, что вызывает множество проблем при отладке программ и еще больше – при их модификации. Но немаловажной проблемой является и то, что логика работы оказывается рассредоточенной, что опять же не сказывается на приложении положительным образом.

Реализация MVC в J2EE-приложении.

Паттерн MVC был предложен для классических настольных приложений, но он практически без изменений может быть использован и для построения приложений J2EE, хотя здесь имеются свои нюансы. Существует две реализации паттерна MVC для J2EE-приложений – это Model1 и Model2. В качестве Вида в каждом из них используются JSP, в качестве МоделиJavaBean. Основное отличие подходов заключается в том, каким образом реализован Контроллер: в Model1 – это JSP, а в Model2сервлет.

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

  • 1 Cложно обеспечить разделение труда между дизайнерами и программистами.
  • 2 Архитектуру Model1 сложно поддерживать, и она не является гибкой, что особенно критично для больших проектов [6].

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

Модель – JavaBeans

Прежде всего, Модель в J2EE-приложениях, как и обычных приложениях, удобно реализовывать в виде совокупности JavaBeans. JavaBean’ом может называться любой класс Java, который удовлетворяет достаточно простым требованиям:

  • все атрибуты класса должны быть защищенными (protected);
  • для каждого атрибута, предназначенного для чтения, должен быть реализован метод вида: PropertyClass getPropertyName(){...};
  • для каждого атрибута, предоставляющего возможность записи должен быть реализован метод вида: setPropertyName(PropertyClass propertyName){...};
  • обязательно должен быть реализован конструктор без параметров.

Для примера давайте разработаем небольшое web-приложение, отображающее новости. Пусть Моделью приложения будут JavaBean-объекты, инкапсулирующие в себе сведения о новостях. Объект News, содержащий в себе одну-единственную новость, является простым объектом JavaBean и имет только два метода (чтение и запись) для всех своих атрибутов:

 public class News {
    protected String caption;
    protected String message;
    protected Date date;
    public String getCaption() {
       return caption;
    }
 }

Второй объект, логически завершающий нашу модель данных, – это NewsTape, который помимо обязательных методов, необходимых JavaBean, имеет дополнительные методы, расширяющие его функциональность, что не запрещено:

 public void addNews(News news) {
       if (news != null) {
           AllNews.add(news);
       }
    }

Связь Вида и Контроллера

Для реализации Вида нам потребуется создать две JSP, одну – для отображения новостей (view.jsp), другую – для их добавления (edit.jsp). Вторая процедура содержит небольшую форму:

 <form action=<%=request.getContextPath()%>/
    <%=request.getAttribute(“action”)%>” method=”post”>
           <input type=”hidden” name=”edited”
              value=<%=request.getAttribute(“edit.number)%>/>
         <table>
              <tr><td>Заговок: </td><td><input type=”text” name=”caption”
  value=<%=request.getAttribute(“edit.caption)%>/></td></tr>
              <tr><td>Сообщение: </td><td><input type=”text”
 name=”message”
  value=<%=request.getAttribute(“edit.message)%>/></td></tr>
              <tr><td colspan=2” align=”center”><input type=”submit”
  name=”Отправить”/></td></tr>
         </table>
 </form>

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

    private void handle(HttpServletRequest aRequest, HttpServletResponse
 aResponse)
  throws ServletException, IOException {
       aRequest.setCharacterEncoding(“utf-8”);
       String target =
 aRequest.getRequestURI().substring(aRequest.getContextPath().length());
       if (target.equals(/)) {
           outputPage(“index.jsp”, aRequest, aResponse);
       } else if (/add”.equals(target)) {
           handleAdd(aRequest, aResponse);
       } else if (/view”.equals(target)) {
           handleView(aRequest, aResponse);
       }
    }

Вы скажете: «Эй! Да мы подобное уже делали!» И я вам отвечу: «Правильно, вы уже реализовывали контроллер на основе сервлета». Контроллер определяет поведение приложения, то есть интерпретирует действия пользователя и обеспечивает изменение состояния модели или выбор другого представления, или то и другое одновременно.

Из-за особенностей работы web-приложений, выбор Вида из Контроллера можно произвести двумя способами: вызвав метод forward у экземпляра класса RequestDispatcher или вызвав метод sendRedirect у экземпляра класса HttpServletResponse. В то же время, воздействия пользователей на «Контроллер», в основном, могут быть переданы посредствам запросов, то есть нажатием на ссылки, или в качестве результатов работы форм. Управление работой приложения происходит благодаря методу handle нашего сервлета. Такой вид организации обработки принято назвать «реализация стратегии Servlet Front паттерна Front Controller». Хотя, если быть более точным, реализован подвид стратегии Servlet Front – Dispatcher in Controller.

Видимо, такое обилие терминов сбивает с толку, поэтому давайте вкратце рассмотрим стратегии реализации паттерна Front Controller, предназначенные для реализации «Контроллера» в паттерне MVC.

  • 1 Стратегия Servlet Front фактически соответствует поведению контроллера в архитектуре Model2, описанной выше. «Контроллер» редко выполняет все функции, но в случае, если это происходит, и функциональность, отвечающая за перенаправление пользователя к другим «Видам», полностью реализована в нем, то такая реализация называется Dispatcher in Controller. Еще один подвид данной стратегии – Base Front, она отличается тем, что реализован не один контроллер, а несколько, причем все они расширяют некий базовый контроллер, в котором реализована базовая функциональность. Применение этой стратегии не всегда оправдано.
  • 2 Стратегия JSP Front фактически соответствует поведению контроллера в архитектуре Model1, описанной выше, и мы не будем на ней останавливаться, потому как она практически нигде не применяется.
  • 3 Mapping Controller – это стратегия, которая тем или иным образом применяется при построении практически любого web-приложения. Технология реализована через дескриптор развертывания приложе ния и имеет следующие подвиды: Physical Resource Mapping – когда ресурс отображается по месту его физического расположения в каталогах, Logical Resource Mapping – когда ресурс отображается в соот ветствии с некоторой логикой группирования функции приложения, и Multiplexed Resource Mapping – это смешивание обоих методов разме щения ресурсов.
  • 4 Стратегия Filter Controller – это реализация контроллера в виде фильтра, который перехватывает все запросы пользователя и в соответствии с внутренней логикой обеспечивает тот или иной бизнеспроцесс.
  • 5 Command and Controller – это стратегия, основанная на применении двух замечательных паттернов, Command и Factory Method, и, на мой взгляд, одна из самых удачных стратегий. Ее применение мы обсудим в одной из наших следующих статей.

Рис. 2

Рис. 2. Схема взаимодействия некоторых объектов электронного магазина.


Извещение об изменениях

Последний вопрос, который необходимо осветить для полного обзора паттерна MVC – это извещение Вида об изменениях состояния Модели'. К сожалению, в большинстве случаев в J2EE нельзя выполнить оповещение «Вида» за исключением случаев, когда приложение построено с применением ряда современных технологий. Примером такой кооперации может служить AJAX.

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

Классическим примером такого поведения может служить электронный магазин, где необходимо обеспечить взаимодействие корзин покупателей, их счетов и склада с товаром. На рис. 2 представлена схема взаимодействия рассмотренных объектов. Пользователь выбирает товар, и он помещается в корзину, корзина уведомляет объект – счет, где формируется стоимость товара и доставки, и склад, где происходит бронирование или подготовка к дополнительному заказу товара у поставщиков.

В основе описанного процесса лежит реализация паттерна Observer. Его цель – это организовать зависимость «один ко многим» между объектами так, чтобы при изменении состояния одного объекта автоматически уведомлялись и обновлялись все его зависимые объекты.

Считается, что Observer является одним из главных паттернов объектно-ориентированного программирования, по крайней мере, он широко применяется в настольных приложениях. Особенно хорошо его влияние видно в приложениях Swing. Для применения этого шаблона необходимо ответить на следующие вопросы:

  • 1 Кто является объектом наблюдения, а кто наблюдателем?
  • 2 Когда объект наблюдения должен послать уведомление своим наблюдателям?
  • 3 Что должен делать наблюдатель при получении уведомления?
  • 4 Как должно начинаться и как заканчиваться взаимодействие для организации наблюдения?

Естественно, что на все эти вопросы уже найдены ответы, поэтому давайте рассмотрим рис. 3, на котором изображена диаграмма классов паттерна Observer.

Рис. 3

Рис. 3. Диаграмма классов паттерна Observer.

В диаграмме определены два интерфейса: обозреватель (Observer) и объект наблюдения (Observable). Класс, реализующий интерфейс Observable, предоставляет методы для подключения и отключения обозревателей, содержит в себе текущий список наблюдателей (атрибут observers), а также имеет метод оповещения всех наблюдателей – notifyObserver(). Итак, используем интерфейсы для создания небольшого примера:

 public interface Observer {
   public void handleEvent();
 }
 public interface Observable {
    public void addObserver(Observer o);
    public void removeObserver(Observer o);
    public void notyfyObserver();
 }

Создадим объект наблюдения – пусть это будет совсем простенькая модель погоды, которая содержит в себе сведения о температуре окружающей среды (атрибут temperature):

 public class Weather implements Observable {
    private Set<Observer> observers = new HashSet();
    private int temperature;
    public void addObserver(Observer o) { observers.add(o); }
    public void removeObserver(Observer o) { observers.remove(o); }
    public void notyfyObserver() {
       for (Observer o : observers) {
           o.handleEvent();
       }
    }
    public Weather() { this.temperature = 0;}
    public int getTemperature() { return temperature; }
    public void setTemperature(int temperature) {
       this.temperature = temperature;
       notyfyObserver();
    }
 }

Теперь создадим класс для ведения наблюдения за температурой, то есть термометр:

 public class Thermometer implements Observer {
    private int temperature;
    private Weather weather;
    public Thermometer(Weather weather) {
       this.weather = weather; this.handleEvent();
    }
    public int getTemperature() { return temperature; }
    public void setWeather(Weather weather) {
       this.weather = weather; this.handleEvent();
    }
    public void handleEvent() {
       if (weather != null) {
           temperature = weather.getTemperature();
           System.out.println(“температура :+ temperature);
       }
    }
 }

Чтобы проверить работоспособность оповещения, попробуйте выполнить следующий код:

       Weather currentWeather = new Weather();
       Thermometer theThermometer1 = new Thermometer(currentWeather);
       Thermometer theThermometer2 = new Thermometer(currentWeather);
       currentWeather.addObserver(theThermometer1);
       currentWeather.addObserver(theThermometer2);
       System.out.println(“Изменение температуры 1”);
       currentWeather.setTemperature(10);
       System.out.println(“Изменение температуры 2”);
       currentWeather.setTemperature(15);

Вы убедитесь в том, что у каждого наблюдателя вызывается метод handleEvent(), что влечет за собой изменение его внутреннего состояния.

Подведем итоги. В этой статье мы рассмотрели реализацию шаблона MVC в J2EE и ряда других паттернов, которые позволяют реализовывать масштабируемые приложения и разобрались в требованиях, предъявляемых к классам так называемых JavaBeans. Кроме того, мы вкратце ознакомились со способами организации Контроллера и обратили свое внимание на процесс оповещения объектов на основе паттерна Observer. Пожалуй, на сегодня хватит. LXF

Литература

  • 1 Krasner G.E., Pope S.T. A Cookbook for Using the Model-View-Controller User Interface Paradigm in Smalltalk-80.// Journal of Object-Oriented Programming. – 1988. – № 3, ч. 1. – С. 26-49.
  • 2 Alexander C., Ishikawa S., Silverstein M., Jacobson M., Fiksdahl-King I. and Angel S. A Pattern Language. – New York: Oxford University Press, 1977. – 1216 с.
  • 3 Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. – СПб.: Питер, 2001. – 368 с.
  • 4 Мак-Карти Д., Мак-Карти М. Программируем командный дух. – СПб.: Символ-Плюс, 2004. – 416 с.
  • 5 Тейт Б. Горький вкус Java: Библиотека программиста. – СПб.: Питер, 2003. – 333 с.
  • 6 Курняван Б. Создание Web-приложений на языке Java. – Москва: Издательство “ЛОРИ”, 2005. – 880 с.
Персональные инструменты
купить
подписаться
Яндекс.Метрика