Практически Groovy
Программирование MVC с помощью шаблонов Groovy
Упростите представление отчетов с помощью среды механизмов шаблонов Groovy
Серия контента:
Этот контент является частью # из серии # статей: Практически Groovy
Этот контент является частью серии:Практически Groovy
Следите за выходом новых статей этой серии.
В последних статьях серии Практически Groovy я показал, что Groovy -- это отличный инструмент для создания приложений, формирующих отчёты. Я приводил пример для составления отчетов по контрольным суммам, чтобы проиллюстрировать основы написания сценариев Ant в Groovy, и отчетов по статистике базы данных, чтобы продемонстрировать преимущества программирования JDBC с помощью GroovySql. Программирование JDBC с помощью GroovySql . Оба эти примера отчетов формируются в сценарии Groovy и оба по своей природе имеют связанные с ними "представления".
В примере отчета по контрольным суммам код представления был несколько запутан. Хуже того, он обещал сложности в поддержке: Если бы мне когда-либо понадобилось изменить какой-либо аспект представления, мне пришлось бы изменить сам код сценария. Поэтому в этом месяце я покажу, как упростить представление отчета с помощью механизма шаблонов Groovy и приложения формирования отчета по контрольным суммам из моей предыдущей статьи.
Механизмы шаблонов подобны XSLT и могут формировать код в любом формате, для которого определен шаблон, в том числе XML, HTML, SQL и Groovy. Подобно JSP, механизмы шаблонов могут упростить выделение элементов представления в отдельные сущности, такие как файл шаблона JSP. Например, если предполагается вывод отчета в XML, вы можете создать шаблон XML, в котором будут содержаться подстановочные символы, заменяемые значениями во время работы. После этого механизм шаблонов преобразует шаблон, считывая его и заполняя подстановочные символы соответствующими значениями. В результате на выходе будет получен документ в формате XML.
Перед тем, как приступать к рассмотрению шаблонов Groovy, я хотел бы вернуться ненадолго к понятию типа String
в Groovy. Все языки сценариев пытаются сделать работу со строками максимально простой. И снова Groovy нас не подводит. Прежде чем мы продолжим, нажмите на пиктограмму Code
в верхней или нижней части этой страницы (или перейдите к разделу Материалы для загрузки) для загрузки исходного кода к этой статье.
Мне не хватает Strings
Возможности работы со строками в обычном коде Java™, к сожалению, очень ограничены, хотя Java 5.0 обещает несколько новых изящных возможностей. Groovy устраняет два наиболее неприятных ограничения String
Java, существующих на сегодняшний день, упрощая запись многострочных переменных и выполнение подстановок во время работы приложения. Несколько простых примеров помогут вам познакомиться с типами String
Groovy, с которыми я буду работать в этой статье.
Если вы указываете многострочную String
в обычном коде Java, вам нужно завершать строки множеством докучливых +
, правильно? Но, как вы можете увидеть в листинге 1, в Groovy этих +
нет, что делает код значительно чище и проще.
Листинг 1. Многострочные строки в Groovy
String example1 = "This is a multiline string which is going to cover a few lines then end with a period."
Как показано в листинге 2, Groovy также поддерживает понятие here-docs. here-doc -- это удобный механизм создания форматированных
String
, например, HTML и XML. Обратите внимание, что синтаксис here-doc не сильно отличается от обычного объявления String
, за исключением того, что в нем обязательно использование тройных кавычек, как в Python.
Листинг 2. Here-docs в Groovy
itext = """ This is another multiline String that takes up a few lines. Doesn't do anything different from the previous one. """
Для реализации подстановок во время выполнения программы в Groovy используются GString
. Если вы не знакомы с ними, позволю себе предположить, что вы уже видели их раньше, и даже, вероятно, использовали их. Например, GString
позволяют выполнять подстановку значений с помощью bash-подобного синтаксиса ${}
. Прелесть GString
состоит в том, что вам не надо даже задумываться о том, что вы используете тип GString
; вы просто пишете код с String
в Groovy, как в коде Java.
Листинг 3. GStrings в Groovy
lang = "Groovy" println "Uncle man, Uncle man, I dig ${lang}."
В листинге 3 я создал переменную lang
и присвоил ей значение "Groovy". Я вывожу String
типа GString
и заменяю место после слова "dig" значением ${lang}
. В реальности этот код вывел бы "Uncle man, Uncle man, I dig Groovy". Стильно, не правда ли?
Подстановка во время выполнения программы фактически является общей чертой динамических языков, и здесь, как обычно, Groovy идёт на шаг впереди.
GString
в Groovy позволяют автоматически вызывать
методы для заменяемых значений, что открывает широчайшие возможности для создания динамических текстов. Например, в листинге 4 я могу вызвать метод для нужной мне переменной (в данном случае метод length()
) на объект типа
String
.
Листинг 4. Автоматический вызов GString
lang = "Groovy" println "I dig any language with ${lang.length()} characters in its name!"
Код в листинге 4 выведет "I dig any language with 6 characters in its name!" В следующем разделе я покажу вам, как использовать функцию вызова Groovy для реализации некоторых более сложных возможностей шаблонов.
Шаблоны Groovy
Работу с шаблонами можно разделить на две основные задачи: Во-первых, нужно создать шаблон; во вторых, нужно реализовать код отображения. Создание шаблонов в среде шаблонов Groovy очень похоже на создание JSP, поскольку вы можете использовать синтаксис JSP. Ключ к созданию этих шаблонов лежит в определении переменных, заменяемых в ходе работы программы. Например, в листинге 5 я определил шаблон для создания GroovyTestCase
.
Листинг 5. Шаблон для создания GroovyTestCases
import groovy.util.GroovyTestCase class <%=test_suite %> extends GroovyTestCase { <% for(tc in test_cases) { println "\tvoid ${tc}() { } " }%> }
Шаблон, показанный в листинге 5, выглядит как файл JSP, поскольку я использовал синтаксис <%
и <%=
. Однако, благодаря гибкости Groovy, вы не ограничены синтаксисом JSP. Вы также можете использовать превосходные GString
Groovy, как показано в листинге 6.
Листинг 6. GStrings в действии
<person> <name first="${p.fname}" last="${p.lname}"/> </person>
В листинге 6 я создал простой шаблон, представляющий документ XML, который определяет коллекцию элементов person
. Как вы можете видеть, шаблон ожидает некоторый объект
p
, у которого будут свойства fname
и lname
.
Шаблоны в Groovy определяются очень просто, как это и должно быть. Шаблоны Groovy - это совсем не ядерная физика. Это просто способ упрощения отделения представления от его модели. Следующий шаг состоит в написании кода для выполнения отображения в процессе работы программы.
Отображение в процессе работы программы
Теперь, когда у меня есть шаблон, мне нужно наложить определенные мной переменные на значения, полученные в процессе работы программы. Как это обычно бывает с Groovy, вещи, которые кажутся сложными, на самом деле очень просты. Все, что нужно сделать -- установить соответствие (map
) ключей, которые являются именами переменных в шаблоне, и ключей, значения которых являются нужными значениями, подставляемыми в процессе работы.
Например, если в простом шаблоне есть переменная favlang
, мне нужно определить map
со значением ключа favlang
. Значением ключа будет мой любимый язык сценариев (в данном случае, конечно же, Groovy).
В листинге 7 я определил этот простой шаблон, а в листинге 8 показан соответствующий код отображения.
Листинг 7. Простой шаблон для демонстрации отображения
My favorite dynamic language is ${favlang}
В листинге 8 показан пример класса, который делает пять вещей, в том числе две очень важные. Можете ли вы назвать их?
Листинг 8. Отображение значений для простого шаблона
package com.vanward.groovy.tmpl import groovy.text.Template import groovy.text.SimpleTemplateEngine import java.io.File class SimpleTemplate{ static void main(args) { fle = new File("simple-txt.tmpl") binding = ["favlang": "Groovy"] engine = new SimpleTemplateEngine() template = engine.createTemplate(fle).make(binding) println template.toString() } }
Отображение значений для шаблона, выполненное в листинге 8, оказалось на удивление простым.
Сначала я создал экземпляр File
, указывающий на шаблон simple-txt.tmpl
.
После этого я создал связывающий объект binding
, который, по существу, является map
. Я отобразил значение
favlang
в шаблоне на String
Groovy. Это отображение - первый важный шаг использования шаблонов в Groovy, и, на самом деле, в любом другом языке, где есть механизм шаблонов.
Затем я создал экземпляр SimpleTemplateEngine
, который является частной реализацией механизма шаблонов в Groovy. После я передал механизму экземпляр шаблона (simple-txt.tmpl
) и объект binding
. Объединение шаблона и соответствующего объекта binding
- это второй важный шаг в листинге 8 и основной вопрос работы с механизмами шаблонов. По сути дела, среда будет отображать значения, найденные в объекте
binding
, на имена в соответствующем шаблоне.
Последним шагом в листинге 8 был вывод результата процесса. Как вы можете видеть, создание объекта binding
и выполнение соответствующего отображения были очень простыми, по крайней мере в этом примере. В следующем разделе я испытаю механизм шаблонов Groovy на более сложном примере.
Более сложный шаблон
В листинге 9 я создал класс Person
, представляющий элемент person
, определенный в листинге 6..
Листинг 9. Класс Person в Groovy
class Person{ age fname lname String toString(){ return "Age: " + age + " First Name: " + fname + " Last Name: " + lname } }
В листинге 10 вы можете увидеть код отображения, который устанавливает соответствие для определенного выше класса Person
.
Листинг 10. Отображение класса Person на шаблон
import java.io.File import groovy.text.Template import groovy.text.SimpleTemplateEngine class TemplatePerson{ static void main(args) { pers1 = new Person(age:12, fname:"Sam", lname:"Covery") fle = new File("person_report.tmpl") binding = ["p":pers1] engine = new SimpleTemplateEngine() template = engine.createTemplate(fle).make(binding) println template.toString() } }
Код, приведенный выше, кажется знакомым, не так ли? На самом деле он похож на код листинга 8, с добавлением строки, создающей экземпляр pers1
. Давайте теперь ещё раз ненадолго вернёмся к шаблону, приведенному в листинге 6. Видите, как в шаблоне указываются свойства fname
и lname
? Я просто создал экземпляр Person
, присвоив свойству fname
значение "Sam" и свойству lname
значение "Covery" .
При выполнении кода, приведенного в листинге 10, на выходе будет сформирован XML , определяющий элемент person
, как видно в листинге 11.
Листинг 11. Вывод шаблона Person
<person> <name first="Sam" last="Covery"/> </person>
Отображение списка
В листинге 5 я определил шаблон для GroovyTestCase
. Если вы посмотрите на шаблон сейчас, вы заметите, что в определении есть некая логика для выполнения итераций по коллекции. В листинге 12 приведен похожий код, но в этом коде имеется логика для отображения list
тестовых случаев.
Листинг 12. Отображение списка тестовых случаев
fle = new File("unit_test.tmpl") coll = ["testBinding", "testToString", "testAdd"] binding = ["test_suite":"TemplateTest", "test_cases":coll] engine = new SimpleTemplateEngine() template = engine.createTemplate(fle).make(binding) println template.toString()
Если посмотреть на листинг 5, можно увидеть, что шаблон ожидает list
с именем "test_cases" -- который я определил в листинге 12 как coll
, содержащий три элемента. Я просто присвоил coll
ключу "test_cases" в объекте binding - и код готов.
Теперь должно быть понятно, насколько просто использовать шаблоны в Groovy. Они также облегчают применение популярного шаблона MVC и, что еще важнее, поддерживают реорганизацию исходного кода в код MVC путём реализации представления. В следующем разделе я покажу, как реорганизовать код примера из предыдущей статьи, применяя полученные здесь навыки.
Реорганизация кода с применением шаблонов
В колонке по написанию сценариев Ant с помощью Groovy я приводил простую утилиту, которая формировала отчёт по контрольным суммам файлов класса. Если помните, я довольно топорно написал XML с помощью println
. Этот код был настолько безобразным, что даже мне пришлось это признать. Убедитесь сами, посмотрев листинг 13.
Листинг 13. Корявый код
nfile.withPrintWriter{ pwriter | pwriter.println("<md5report>") for(f in scanner){ f.eachLine{ line | pwriter.println("<md5 class='" + f.path + "' value='" + line + "'/>") } } pwriter.println("</md5report>") }
Вспомним еще раз контекст задачи: код, приведенный в листинге 13, принимает некоторые данные и записывает их в файл (экземпляр nfile
с помощью PrintWriter
. Обратите внимание, что я жёстко закодировал компонент представления отчёта (XML) в
println
. Проблема этого подхода в отсутствии гибкости. Если когда-нибудь позже мне понадобится внести изменения, мне нужно будет вмешиваться в логику сценария Groovy. Хуже того - представьте, что изменения захочет внести непрограммист. Код Groovy будет для него поистине устрашающим.
Выделение части представления сценария в шаблон упростит обслуживание, поскольку изменения шаблона будет более естественным процессом для каждого, - поэтому именно это я и сделаю.
Определение шаблона
Я начну с определения шаблона - он будет похож на предполагаемый результирующий документ, с некоторой логикой для итерирования по коллекции классов
Листинг 14. Реструктуризация старого кода в шаблон
<md5report> <% for(clzz in clazzes) { println "<md5 class=\"${clzz.name}\" value=\"${clzz.value}\"/>" }%> </md5report>
Шаблон, определенный в листинге 14, похож на шаблон для GroovyTestCase
в том, что в него входит логика итерирования по коллекции. Обратите внимание, что я смешал синтаксис JSP c GString
.
Написание кода отображения
Теперь, когда определен шаблон, нужно написать код отображения в процессе работы программы. Мне нужно заменить старую логику записи файла в коде формированием коллекции объектов ChecksumClass
и помещением этих объектов в объект binding
.
После этого модель становится классом ChecksumClass
, определенным в листинге 15.
Листинг 15. Определение CheckSumClass в Groovy
class CheckSumClass{ name value String toString(){ return "name " + name + " value " + value } }
Классы в Groovy определяются очень просто, не правда ли?
Создание коллекции
Теперь мне нужно изменить раздел кода, ранее выполнявший вывод в файл - на этот раз заменив его логикой, наполняющей список новыми
ChecksumClass
, как показано в листинге 16.
Листинг 16. Измененный код, создающий коллекцию ChecksumClasses
clssez = [] for(f in scanner){ f.eachLine{ line | iname = formatClassName(bsedir, f.path) clssez << new CheckSumClass(name:iname, value:line) } }
Из листинга 16 видно, как просто использовать синтаксис, подобный Ruby, для добавления объектов в list
- и это на самом деле
чрезвычайно удобно. Сначала я создал list
с помощью синтаксиса []
. После этого я использовал краткую мнемонику цикла
for
, после которой указал итератор с замыканием. Замыкание обрабатывает каждый line
, который в данном случае является значением контрольной суммы, и создаёт экземпляр только что определенного CheckSumClass
(с помощью конструктора, автоматически созданного Groovy) и добавляет его в коллекцию. Неплохо - и приятно в написании.
Добавление отображения шаблона
Последнее, что мне нужно сделать - добавить код механизма шаблона. Этот код будет выполнять отображение в процессе работы программы и запись отформатированного соответствующим образом шаблона в исходный файл, как показано в листинге 17.
Листинг 17. Реструктуризация кода с отображением шаблона
fle = new File("report.tmpl") binding = ["clazzes": clzzez] engine = new SimpleTemplateEngine() template = engine.createTemplate(fle).make(binding) nfile.withPrintWriter{ pwriter | pwriter.println template.toString() }
Теперь код, приведенный в листинге 17, не должен быть для вас новостью. Я взял list
из листинга 16 и поместил его в объект binding
. После этого я взял объект nfile
и записал соответствующий вывод отображенного шаблона из листинга 14.
Перед тем, как свести всё вместе в листинге 18, вам, возможно, захочется ещё раз взглянуть на тот корявый код из листинга 13, с которого мы начали.. И, вот, для сравнения, как выглядит реорганизованный код:
Листинг 18. Смотрите! Получилось не так плохо!
/** * */ buildReport(bsedir){ ant = new AntBuilder() scanner = ant.fileScanner { fileset(dir:bsedir) { include(name:"**/*class.md5.txt") } } rdir = bsedir + File.separator + "xml" + File.separator file = new File(rdir) if(!file.exists()){ ant.mkdir(dir:rdir) } nfile = new File(rdir + File.separator + "checksum.xml") clssez = [] for(f in scanner){ f.eachLine{ line | iname = formatClassName(bsedir, f.path) clssez << new CheckSumClass(name:iname, value:line) } } fle = new File("report.tmpl") binding = ["clazzes": clzzez] engine = new SimpleTemplateEngine() template = engine.createTemplate(fle).make(binding) nfile.withPrintWriter{ pwriter | pwriter.println template.toString() } }
Итак, хотя я и не претендовал на написание изящного кода, получившийся код определенно не такой безобразный, как старый. И теперь, чтобы завершить начатое, мне остаётся заменить несколько старых кривых println
более красивым кодом шаблона Groovy. (Эксперты в реорганизации кода отметят, что для улучшения кода я мог бы применить метод Extract.)
Заключение
Я надеюсь, в этом месяце мне удалось расширить ваше представление о Groovy. Если говорить более серьезно, среда шаблонов Groovy -- это очень мощная замена кодированию Java в случаях, когда нужно быстро написать простое приложение с каким-либо представлением. Шаблоны являются отличной абстракцией и, при правильном использовании, могут значительно упростить поддержку вашего приложения в долгосрочной перспективе.
В следующем месяце я собираюсь показать вам, как использовать Groovy для создания Web-приложений посредством Groovlets. А пока - удачной разработки шаблонов с помощью Groovy!
Ресурсы для скачивания
- этот контент в PDF
- Образец кода (j-pg02155-source.zip | 2.18 KB)
Похожие темы
- Оригинал статьи MVC programming with Groovy templates (EN).
- Нажмите на пиктограмму Code в верхней или нижней части статьи (или перейдите к разделу Материалы для загрузки) для загрузки примеров кода, использованных в этой статье.
- Отличный обзор шаблонов MVC приводит Малькольм Дэвис в статье Struts, реализация MVC с открытым исходным кодом (EN) (developerWorks, февраль 2001 г.).
- Подробнее узнать о Cheetah можно из статьи Шаблоны Python с использованием Cheetah (EN) (OnLamp.com, январь 2005 г.).
- Если вы любитель Ruby, ознакомьтесь с ERB - отличным механизмом шаблонов для Ruby.(EN)
- Статьи по всем аспектам программирования на Java можно найти на сайте developerWorks, в разделе Технология Java.
- Также познакомьтесь со страницей учебных пособий раздела Java, где можно найти полный список бесплатных учебных пособий по Java от developerWorks.
Комментарии
Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.