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

LXF94:Команды и фабрики

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
(Команды)
(Команды)
Строка 22: Строка 22:
 
  }
 
  }
  
Выше представлен простой интерфейс Command всего с одним методом execute(), который и олицетворяет идею одноименного паттерна. [[Image:Java 6 1.jpg|left|300px|trumb|Рис. 1. Диаграмма классов.]] Он будет нашим стандартым интерфейсом. Более сложная реализация интерфейса включает метод unexecute(). Класс, реализующий интерфейс Command, инкапсулирует в методе execute() обозначенные выше атомарные операции, а в методе unexecute() реализуется механизм отмены. Часто методы execute() и unexecute() называют do() и undo(), соответственно.
+
Выше представлен простой интерфейс Command всего с одним методом execute(), который и олицетворяет идею одноименного паттерна. Он будет нашим стандартым интерфейсом. Более сложная реализация интерфейса включает метод unexecute(). Класс, реализующий интерфейс Command, инкапсулирует в методе execute() обозначенные выше атомарные операции, а в методе unexecute() реализуется механизм отмены. Часто методы execute() и unexecute() называют do() и undo(), соответственно.
 +
 
 +
[[Image:Java 6 1.jpg|left|300px|trumb|Рис. 1. Диаграмма классов.]]
  
 
Выделим команды, которые нам необходимо реализовать (отметим удачное разделение на методы): Add, Auth, Edit, View, Remove. Учитывая то, что у наших команд будут некоторые идентичные методы и атрибуты, предлагаю создать абстрактный класс AbstractHTTPCommand, в котором они будут собраны. Класс будет реализовывать интерфейс Command, и его наследование автоматически позволит обеспечить необходимый уровень интеграции. Итак, в основе каждой команды будет лежать абстрактный класс AbstractHTTPCommand, в котором реализован интерфейс для выполнения операций (на Рис. 1 вы можете видеть диаграмму классов команд нашего приложения).
 
Выделим команды, которые нам необходимо реализовать (отметим удачное разделение на методы): Add, Auth, Edit, View, Remove. Учитывая то, что у наших команд будут некоторые идентичные методы и атрибуты, предлагаю создать абстрактный класс AbstractHTTPCommand, в котором они будут собраны. Класс будет реализовывать интерфейс Command, и его наследование автоматически позволит обеспечить необходимый уровень интеграции. Итак, в основе каждой команды будет лежать абстрактный класс AbstractHTTPCommand, в котором реализован интерфейс для выполнения операций (на Рис. 1 вы можете видеть диаграмму классов команд нашего приложения).
Строка 83: Строка 85:
  
 
Остальные команды реализуются аналогичным образом. Исключение из общего процесса рефакторинга кода составит команда ViewHTTPCommand, для которой уже реализована большая часть функционала:
 
Остальные команды реализуются аналогичным образом. Исключение из общего процесса рефакторинга кода составит команда ViewHTTPCommand, для которой уже реализована большая часть функционала:
 +
 +
public void execute() throws Exception {
 +
  makeDataToView();
 +
  outputPage(this.getViewPath(), aRequest, aResponse);}
 +
 +
Выполнение любой команды унифицировано и будет выглядеть следующим образом:
 +
 +
  Command cmd;
 +
  cmd = new EditHTTPCommand(this.getServletContext(),
 +
        aRequest, aResponse, “edit.jsp”, “view.jsp”, “view.jsp”);
 +
  try {
 +
      cmd.execute();
 +
  } catch (Exception e) {
 +
      e.printStackTrace();
 +
}
 +
 +
После создания всех команд контроллер должен преобразиться: лишние методы уйдут, а его главная функция – управление – будет восстановлена:
 +
 +
private void handle(HttpServletRequest aRequest,
 +
    HttpServletResponse aResponse) throws ServletException, IOException {
 +
    aRequest.setCharacterEncoding(“utf-8”);
 +
    String target = aRequest.getRequestURI().substring(aRequest.getContextPath().length());
 +
    Command cmd;
 +
    if (target.equals(“/”)) {
 +
      cmd = new ViewHTTPCommand(this.getServletContext(),aRequest, aResponse, “index.jsp”, null, null);
 +
    } else if (“/add”.equals(target)) {
 +
      cmd = new AddHTTPCommand(this.getServletContext(), Request, aResponse, “edit.jsp”, “view.jsp”, null);
 +
    } else if (“/view”.equals(target)) {
 +
      cmd = new ViewHTTPCommand(this.getServletContext(), aRequest, aResponse, “view.jsp”, null, null);
 +
    } else if (“/edit”.equals(target)) {
 +
      cmd = new EditHTTPCommand(this.getServletContext(), aRequest, aResponse, “edit.jsp”, “view.jsp”, “view.jsp”);
 +
    } else if (“/remove”.equals(target)) {
 +
      cmd = new RemoveHTTPCommand(this.getServletContext(), aRequest, aResponse, null, “view.jsp”, null);
 +
    } else if (“/auth”.equals(target)) {
 +
      cmd = new AuthHTTPCommand(this.getServletContext(), aRequest, aResponse, “auth.jsp”, “index.jsp”, “auth.jsp”);
 +
    } else {
 +
      cmd = new ViewHTTPCommand(this.getServletContext(), aRequest, aResponse, “view.jsp”, null, null);
 +
    }
 +
    try {
 +
      cmd.execute();
 +
    } catch (Exception e) {
 +
        // oopst...
 +
        e.printStackTrace();
 +
    }
 +
}
 +
 +
С одной стороны, от внедрения паттерна Command в наше web-приложение мы получили следующие преимущества: управление сосредоточилось в одном месте (в методе handle(), представленном выше), код стал более структурированным. С другой стороны, мы пока не добились возможности полного отторжения продукта от разработчика (при котором исходный код не передается заказчику), так как для изменения функциональности или доработки приложения (как минимум, при добавлении новой команды) необходимо производить перекомпиляцию.
 +
 +
В принципе, ничего непреодолимого нет, в любую программу можно внести исправления, но в одни они вносятся проще, чем в другие.
 +
Поставим себе задачу сделать внесение исправлений в наше приложение простым. Для этого нам надо:
 +
 +
* Вынести связи между адресами, командами и видами за пределы приложения.
 +
* Заложить возможность создания экземпляра команды при наличии ее названия.
 +
* Изменить процесс вызова конкретной команды, чтобы при расширении приложения (дополнении новых команд) не требовалось вносить изменения в уже существующий код.
  
 
=== Хранение настроек команд ===
 
=== Хранение настроек команд ===

Версия 07:21, 22 марта 2008

Содержание

Команды и фабрики

ЧАСТЬ 6 Антон Черноусов готов познакомить вас с очередной партией паттернов, которые помогут сделать ваши приложения еще более гибкими и расширяемыми.

Вместо предисловия

В предыдущей статье мой коллега Александр Бабаев рассмотрел вопросы организации и использования БД в Java-приложениях, и в том числе вопросы подключения к БД посредством ConnectionPool.

Сегодня мы рассмотрим применение двух паттернов, безусловно, оказавших огромное воздействие на проектирование систем – Command и Factory Method. Их применение позволит сделать ваше приложение расширяемым.

Команды

В LXF92 мы кратко описали стратегии, предназначенные для реализации «Контроллера», и обещали более подробно рассмотреть стратегию Command and Controller. Чтобы выполнить это обещание, нам придется сначала познакомиться с паттерном Command.

Задача, которая стоит перед контроллером (сервлетом) при получении управляющего сигнала, как правило, заключается в выполнении последовательности действий, часто атомарной (т.е. обрабатываемой как единое целое). Например, в сервлете AddressBookServlet, реализованном в предыдущей статье, метод handleEdit вызывается, когда адрес на который обращается пользователь – это “/edit”.

К сожалению, на примере AddressBookServlet мы видим, что при увеличении функциональности web-приложения растёт и количество методов, реализованных в сервлете; класс «засоряется», код становится менее структурированным и читабельным. Решить проблемы с кодом можно, «обернув» методы в специальные классы, которые будут выполнять атомарные операции и предоставлять сервлету стандартный интерфейс, предназначенный для этих целей. Для выполнения поставленной задачи воспользуемся паттерном Command.

Команды удобны прежде всего тем, что они маскируют конкретную реализацию, находящуюся за интерфейсной прослойкой. Интерфейс остается одним и тем же, независимо от того, с чем работает команда [1].

public interface Command {
   public void execute() throws Exception;
}

Выше представлен простой интерфейс Command всего с одним методом execute(), который и олицетворяет идею одноименного паттерна. Он будет нашим стандартым интерфейсом. Более сложная реализация интерфейса включает метод unexecute(). Класс, реализующий интерфейс Command, инкапсулирует в методе execute() обозначенные выше атомарные операции, а в методе unexecute() реализуется механизм отмены. Часто методы execute() и unexecute() называют do() и undo(), соответственно.

Рис. 1. Диаграмма классов.

Выделим команды, которые нам необходимо реализовать (отметим удачное разделение на методы): Add, Auth, Edit, View, Remove. Учитывая то, что у наших команд будут некоторые идентичные методы и атрибуты, предлагаю создать абстрактный класс AbstractHTTPCommand, в котором они будут собраны. Класс будет реализовывать интерфейс Command, и его наследование автоматически позволит обеспечить необходимый уровень интеграции. Итак, в основе каждой команды будет лежать абстрактный класс AbstractHTTPCommand, в котором реализован интерфейс для выполнения операций (на Рис. 1 вы можете видеть диаграмму классов команд нашего приложения).

Определим общие методы для абстрактного класса: initCommand() – предназначен для инициализации команды, makeDataToView() – для подготовки данных для отображения в случае их изменения, outputPage() – метод для переадресации пользователя (он будет перенесен из AddressBookServlet без изменений) и другие. Ниже представлена реализация методов initCommand() и makeDataToView():

protected void initCommand(ServletContext sc, HttpServletRequest
aRequest,
   HttpServletResponse aResponse, String viewPath,
   String resultPath, String errorPath) {
 this.setSc(sc);
 this.setARequest(aRequest);
 this.setAResponse(aResponse);
 this.setResultPath(resultPath);
 this.setErrorPath(errorPath);
 this.setViewPath(viewPath);
}
public void makeDataToView() {
 Map<String, String> numbers = new HashMap<String, String>();
 Map<String, String> comments = new HashMap<String, String>();
 for (Map.Entry<String, Contact> entry :
    _addressBook.getContacts().entrySet()) {
   numbers.put(entry.getKey(), entry.getValue().getNumber());
   comments.put(entry.getKey(), entry.getValue().getComment());
 }
 aRequest.setAttribute(“numbers”, numbers);
 aRequest.setAttribute(“comments”, comments);
 if (aRequest.getAttribute(“message”) == null) {
   aRequest.setAttribute(“message”, “”);
 }
}

Метод makeDataToView() – существенная часть метода handleView() класса AddressBookServlet. Вы можете удивиться, для чего метод initCommand() содержит так много параметров; это необходимо для того, чтобы в момент создания команды полностью передать ей всю необходимую для ее выполнения информацию. Параметры viewPath, resultPath и errorPath появились не случайно – они предназначены для адресов (видов, если использовать термины MVC), используемых в случае простого отображения данных, удачного и, соответственно, неудачного выполнения команды.

Перейдем к реализации самих команд. Рассмотрим, например, метод execute() класса EditHTTPCommand. Он практически полностью соответствует первоначальному методу handleEdit класса AddressBookServlet, исключая переадресацию пользователя на конкретный вид.

public void execute() throws Exception {
   if (aRequest.getParameter(“number”) == null) {
    _addressBook.removeContactByNumber(aRequest.getParameter(“number”));
    aRequest.setAttribute(“message”,“Не определено, что редактировать”);
    outputPage(this.getErrorPath(), aRequest, aResponse);
   } else if (aRequest.getParameter(“edited”) != null) {
    _addressBook.editContactByNumber(aRequest.getParameter(“edited”),
       aRequest.getParameter(“name”),
       aRequest.getParameter(“number”),
       aRequest.getParameter(“comment”));
    aRequest.setAttribute(“message”, “Контакт \”” +
       aRequest.getParameter(“name”) + “\” отредактирован”);
    makeDataToView();
    outputPage(this.getResultPath(), aRequest, aResponse);
 } else {
    Contact contact = _addressBook.getContactByNumber(aRequest.getParameter(“number”));
    aRequest.setAttribute(“action”, “edit”);
    aRequest.setAttribute(“edit.name”, contact.getName());
    aRequest.setAttribute(“edit.number”, contact.getNumber());
    aRequest.setAttribute(“edit.comment”, contact.getComment());
    outputPage(this.getViewPath(), aRequest, aResponse);
  }
}

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

public void execute() throws Exception {
  makeDataToView();
  outputPage(this.getViewPath(), aRequest, aResponse);}

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

  Command cmd;
  cmd = new EditHTTPCommand(this.getServletContext(),
        aRequest, aResponse, “edit.jsp”, “view.jsp”, “view.jsp”);
  try {
      cmd.execute();
  } catch (Exception e) {
      e.printStackTrace();
}

После создания всех команд контроллер должен преобразиться: лишние методы уйдут, а его главная функция – управление – будет восстановлена:

private void handle(HttpServletRequest aRequest,
   HttpServletResponse aResponse) throws ServletException, IOException {
   aRequest.setCharacterEncoding(“utf-8”);
   String target = aRequest.getRequestURI().substring(aRequest.getContextPath().length());
   Command cmd;
   if (target.equals(“/”)) {
      cmd = new ViewHTTPCommand(this.getServletContext(),aRequest, aResponse, “index.jsp”, null, null);
   } else if (“/add”.equals(target)) {
      cmd = new AddHTTPCommand(this.getServletContext(), Request, aResponse, “edit.jsp”, “view.jsp”, null);
   } else if (“/view”.equals(target)) {
      cmd = new ViewHTTPCommand(this.getServletContext(), aRequest, aResponse, “view.jsp”, null, null);
   } else if (“/edit”.equals(target)) {
      cmd = new EditHTTPCommand(this.getServletContext(), aRequest, aResponse, “edit.jsp”, “view.jsp”, “view.jsp”);
   } else if (“/remove”.equals(target)) {
      cmd = new RemoveHTTPCommand(this.getServletContext(), aRequest, aResponse, null, “view.jsp”, null);
   } else if (“/auth”.equals(target)) {
      cmd = new AuthHTTPCommand(this.getServletContext(), aRequest, aResponse, “auth.jsp”, “index.jsp”, “auth.jsp”);
   } else {
      cmd = new ViewHTTPCommand(this.getServletContext(), aRequest, aResponse, “view.jsp”, null, null);
   }
   try {
      cmd.execute();
   } catch (Exception e) {
       // oopst...
       e.printStackTrace();
   }
}

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

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

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

Хранение настроек команд

Создание экземпляра класса по имени

Фабрика команд

Что дальше?


Литература

  1. Тейт, Б. Горький вкус Java: Библиотека программиста. – СПб: Питер, 2003. – 333 с.
  2. Мартин, Р.С. Быстрая разработка программ: принципы, примеры, практика. – М.: Издательский дом «Вильямс», 2004. – 752 с.: ил.
Персональные инструменты
купить
подписаться
Яндекс.Метрика