Программисты часто обращаются к таким языкам, как Groovy, для создания кратких утилит, быстрого написания тестового кода, и даже для создания компонентов, из которых составляются крупные Java-приложения. Причиной этого служит присущая Groovy возможность сократить большую часть ненужного кода и снизить его сложность, свойственную системам на базе Java. Лаконичный, но гибкий синтаксис Groovy освобождает разработчиков от обычных конструкций Java, которые необходимы для компиляции кода, но вовсе не обязательно помогают выразить, что же программа реально пытается выполнить. Более того, менее строгая типизация Groovy снижает сложность восприятия кода. Это происходит благодаря сокращению интерфейсов и суперклассов, которые необходимы в обычных Java-приложениях для поддержки обычного поведения среди отдельных строго определенных типов.
В качестве примера сокращения лишнего кода простого старого Java-приложения с помощью Groovy будет использован код из книги Брюса Тейта (Bruce Tate) и Джастина Гетланда (Justin Ghetland) Spring: записная книжка разработчика (Ресурсы), в которой представлена инверсия управления с помощью Spring. По мере рассмотрения каждого примера кода на Java будет делаться сравнение с соответствующим функционально эквивалентным исходным кодом на Groovy. Думаю, что довольно быстро станет понятно, насколько Groovy делает код приложения яснее и сокращает различные аспекты программирования на Java, которые излишни и не обязательно отображают поведение приложения.
В первой главе книги Брюса и Джастина они создают простое приложение для магазина велосипедов, в котором есть
четыре различных класса. Сначала рассмотрим простой класс JavaBean
под названием Bike, который представляет собой велосипед на складе. Затем идет тип магазина велосипедов под названием RentABike, который содержит коллекцию велосипедов (Bike).
Есть также класс для отображения списка велосипедов с удачным названием CommandLineView, который зависит от типа RentABike. И, наконец, есть класс, отвечающий за сборку частей для создания
работающего приложения. Для полной конфигурации класса CommandLineView с
помощью типа RentABike без ручного написания кода используется Spring.
Класс, который в листинге 1 представляет велосипед, реализован как простой компонент JavaBean в обычном коде Java. Это типичный представитель сотен классов, которые пишут разработчики на Java. Ничего необычного в этих компонентах JavaBean нет: свойства объявлены как private,
а доступ к ним осуществляется через методы getter и setter, объявленные как public .
Листинг 1. Компонент JavaBean Bike, реализованный в коде на Java
import java.math.BigDecimal;
public class Bike {
private String manufacturer;
private String model;
private int frame;
private String serialNo;
private double weight;
private String status;
private BigDecimal cost;
public Bike(String manufacturer, String model, int frame,
String serialNo, double weight, String status) {
this.manufacturer = manufacturer;
this.model = model;
this.frame = frame;
this.serialNo = serialNo;
this.weight = weight;
this.status = status;
}
public String toString() {
return "com.springbook.Bike : " +
"manufacturer -- " + manufacturer +
"\n: model -- " + model +
"\n: frame -- " + frame +
"\n: serialNo -- " + serialNo +
"\n: weight -- " + weight +
"\n: status -- " + status +
".\n"; }
public String getManufacturer() { return manufacturer; }
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getModel() { return model; }
public void setModel(String model) { this.model = model; }
public int getFrame() { return frame; }
public void setFrame(int frame) { this.frame = frame; }
public String getSerialNo() { return serialNo; }
public void setSerialNo(String serialNo) { this.serialNo = serialNo; }
public double getWeight() { return weight; }
public void setWeight(double weight) { this.weight = weight; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public BigDecimal getCost() { return cost; }
public void setCost(BigDecimal cost) {
this.cost = cost.setScale(3,BigDecimal.ROUND_HALF_UP);
}
}
|
Ого! Листинг 1 -- это маленький пример всего с одним конструктором и шестью свойствами, а код почти полностью заполнил страницу браузера! Листинг 2 демонстрирует тот же компонент JavaBean, определенный в Groovy:
Листинг 2. Компонент GroovyBean Bike
class Bike {
String manufacturer
String model
Integer frame
String serialNo
Double weight
String status
BigDecimal cost
public void setCost(BigDecimal newCost) {
cost = newCost.setScale(3, BigDecimal.ROUND_HALF_UP)
}
public String toString() {
return """Bike:
manufacturer -- ${manufacturer}
model -- ${model}
frame -- ${frame}
serialNo -- ${serialNo}
"""
}
}
|
Какой из них менее избыточен?
Версия на Groovy гораздо меньше, поскольку семантика свойств по умолчанию в Groovy автоматически
определяет private-поле
со средствами доступа и мутаторами, объявленными как public. Например,
свойство model из раннего листинга
теперь имеет эквивалент в виде метода getModel() и setModel(),
который определяется автоматически. Можно увидеть, каким эта технология обладает преимуществом. Она позволяет сократить два требующих ручного написания метода
на каждое свойство, определяемое в типе! Это также иллюстрирует и повторяющаяся тема в Groovy: делайте общие соглашения в коде простыми.
Более того, с помощью Groovy
функциональность класса по умолчанию выражается короче по сравнению с тем, что необходимо писать
явно в обычном коде на Java (как в листинге 1).
Когда есть необходимость сделать нечто особенное с конструктором либо с
методом getter или setter, Groovy демонстрирует неоспоримые преимущества, поскольку смысл
поведения программы становится немедленно понятен. Надо просто взглянуть на код! Например,
в листинге 2 легко увидеть, что метод setCost() собирается
масштабировать свойство cost до трех десятичных позиций.
Сравните этот скромный по размерам код на Groovy с исходным кодом на Java в листинге 1. Можно ли во время первого прочтения листинга сразу заметить, что метод setCost()
обладает специальной встроенной функциональностью? Если не искать специально, то это легко упустить!
Контрольный пример для класса Bike в листинге 3 демонстрирует
использование автоматически генерируемого метода доступа. В духе дальнейшего упрощения
для частых задач программирования этот пример также использует более удобную нотацию Groovy для доступа к свойствам:
точка имя свойства. Соответственно, можно
ссылаться на свойство model либо
через метод getModel(), либо, более кратко, как b.model.
Листинг 3. Контрольный пример для класса Bike в Groovy
class BikeTest extends GroovyTestCase {
void testBike() {
// Groovy way to initialize a new object
def b = new Bike(manufacturer:"Shimano", model:"Roadmaster")
// explicitly call the default accessors
assert b.getManufacturer() == "Shimano"
assert b.getModel() == "Roadmaster"
// Groovier way to invoke accessors
assert b.model == "Roadmaster"
assert b.manufacturer == "Shimano"
}
}
|
Заметим также, что в приведенных выше примерах на Groovy не нужно было определять конструктор, который принимает все шесть свойств, как это делается в Java-конструкторе, определенном в листинге 1. Кроме того, не нужно было генерировать для поддержки контрольного примера еще один конструктор, который принимает только два аргумента. Семантика Groovy для задания свойств объекта исключает необходимость в этих излишних и утомительных конструкторах с различными комбинациями аргументов, которые нужны лишь для инициализации переменных.
В предыдущем разделе GroovyBean-компонент Bike использовал преимущества семантики свойств и конструкторов Groovy для удаления избыточных строк из исходного кода. В данном разделе Groovy-версия учета велосипедов будет также использовать дополнительные преимущества для сокращения избыточности кода. Это "утиная типизация" (duck-typing) для полиморфизма, расширения классов-коллекций и перегрузка операторов.
В Java-приложении интерфейс под названием RentABike использовался для определения public-методов, поддерживаемых магазином велосипедов. Как видно из листинга 4, RentABike определяет некоторые простые методы
для возврата одного велосипеда (Bike) или списка всех велосипедов
в магазине:
Листинг 4. Интерфейс RentABike, определенный в Java
import java.util.List;
public interface RentABike {
List getBikes();
Bike getBike(String serialNo);
void setStoreName(String name);
String getStoreName();
}
|
Этот интерфейс обеспечивает полиморфное поведение и, следовательно, гибкость в двух важных сценариях. Во-первых, если принято решение изменить реализацию (скажем, с ArrayList на базу данных), остаток приложения изолируется от этого изменения. Также использование интерфейсов
обеспечивает гибкость во время модульного тестирования. Например, если принято решение использовать для приложения базу данных (БД),
то легко можно создать мнимую реализацию типа, который не зависит от реальной БД.
Листинг 5 демонстрирует простую старую реализацию на Java интерфейса RentABike
с использованием ArrayList для хранения экземпляров
класса Bike:
Листинг 5. Реализация интерфейса RentABike на Java
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListRentABike implements RentABike {
private String storeName;
final List bikes = new ArrayList();
public void setStoreName(String name) {
this.storeName = name;
}
public String getStoreName() {
return storeName;
}
public ArrayListRentABike(String storeName) {
this.storeName = storeName;
bikes.add(new Bike("Shimano", "Roadmaster", 20, "11111", 15, "Fair"));
bikes.add(new Bike("Cannondale", "F2000 XTR", 18, "22222",12, "Excellent"));
bikes.add(new Bike("Trek","6000", 19, "33333", 12.4,"Fair"));
}
public String toString() { return "com.springbook.RentABike: " + storeName; }
public List getBikes() { return bikes; }
public Bike getBike(String serialNo) {
Iterator iter = bikes.iterator();
while(iter.hasNext()) {
Bike bike = (Bike)iter.next();
if(serialNo.equals(bike.getSerialNo())) return bike;
}
return null;
}
}
|
Теперь сравним Java-код в листинге 4 и 5 с кодом на Groovy в листинге 6. Версия
на Groovy разумно исключает потребность в интерфейсе RentABike
!
Листинг 6. Реализация ArrayListRentABike на Groovy
public class ArrayListRentABike {
String storeName
List bikes = []
public ArrayListRentABike(){
// add new instances of Bike using Groovy's initializer syntax
bikes << new Bike(manufacturer:"Shimano", model:"Roadmaster",
frame: 20, serialNo:"11111", weight:15, status:"Fair")
bikes << new Bike(manufacturer:"Cannondale", model:"F2000",
frame: 18, serialNo:"22222", weight:12, status:"Excellent")
bikes << new Bike(manufacturer:"Trek", model:"6000",
frame: 19, serialNo:"33333", weight:12.4, status:"Fair")
}
// Groovy returns the last value if no return statement is specified
public String toString() { "Store Name:=" + storeName }
// Find a bike by the serial number
def getBike(serialNo) { bikes.find{it.serialNo == serialNo} }
}
|
Groovy, подобно другим динамическим языкам, например, Smalltalk или Ruby, поддерживает
полиморфизм с "утиной типизацией", то есть если в период исполнения программы объект ведет себя как утка, то
его и считают уткой. Таким образом поддерживается полиморфизм без интерфейсов. В реализации ArrayListRentABike на Groovy не только меньше
строк кода, но и снижена его сложность, поскольку там нужно создать и поддерживать на один модуль меньше.
Это довольно серьезное сокращение избыточности!
В дополнение к "утиной типизации", семантика свойств по умолчанию в листинге
6 кратко определяет два простых свойства storeName и
bikes как имеющие методы getter и setter. Это то же самое преимущество,
которое было продемонстрировано в сравнении JavaBean-GroovyBean на примере листингов 1 и 2.
Более того, листинг 6 также иллюстрирует еще одну возможность Groovy
по снижению избыточности кода -- перегрузку операторов. Обратите внимание, как можно использовать оператор << вместо метода add()
. Читаемость кода повысилась благодаря удалению одного уровня вложенных скобок. Это
еще одна из
многих внешне малозаметных возможностей Groovy, которые обеспечивают читаемость кода и
делают его менее избыточным.
"Утиная типизация" и семантика свойств в Groovy помогают убрать помехи и сократить число строк
исходного кода. Однако помехи можно также убрать и путем увеличения прозрачности. В листинге 6 обратите внимание на то, как
новые объекты Bike создаются в конструкторе ArrayListRentABike. Синтаксис имени и инициализатора значения в Groovy
слегка более многословны, чем в Java, но
этот дополнительный код вносит ясность. Становится немедленно ясно, для каких значений какие
свойства инициализируются. Сравните это с Java-версией в листинге 5. Не
глядя на исходный код JavaBean-компонента Bike, легко ли вспомнить, что за параметр frame и что такое weight для new Bike("Shimano", "Roadmaster", 20, "11111",
15, "Fair")? Невозможно, хотя это только что написано!
Компактное представление магазина велосипедов в духе Groovy
Итак, мы сравнили объект Bike и типы для магазина велосипедов в языках Java и Groovy.
Теперь настало время
пристальнее взглянуть на класс view (представление) в магазине велосипедов. В листинге 7 класс view имеет одно свойство под названием rentaBike, которое
ссылается на интерфейс RentABike и демонстрирует Java-версию полиморфизма в действии. Поскольку Java требует для всех свойств класса объявление типа,
то вместо создания кода для конкретной реализации создается
интерфейс, который изолирует этот класс от
изменений в реализации RentABike. Это хорошая и надежная методика программирования на Java.
Листинг 7. Java-версия представления в магазине велосипедов
public class CommandLineView {
private RentABike rentaBike;
public CommandLineView() {}
public void setRentaBike(RentABike rentaBike) {
this.rentaBike = rentaBike;
}
public RentABike getRentaBike() { return this.rentaBike; }
public void printAllBikes() {
System.out.println(rentaBike.toString());
Iterator iter = rentaBike.getBikes().iterator();
while(iter.hasNext()) {
Bike bike = (Bike)iter.next();
System.out.println(bike.toString());
}
}
}
|
Сравнив Java-версию класса view в листинге 7 с Groovy-версией в Листинге 8, заметим, что свойство rentaBike объявлено с помощью ключевого слова def.
Это и есть "утиная типизация" в действии. Как и в Java-версии, это хорошая практика создания программ,
поскольку представление не привязано к
конкретной реализации. Но этого разделения также удалось добиться без определения
интерфейса.
Листинг 8. CommandLineView в Groovy
public class CommandLineView {
def rentaBike // no interface or concrete type required, duck typing in action
def printAllBikes() {
println rentaBike
rentaBike.bikes.each{ println it} // no iterators or casting
}
}
|
Как и в случае объекта Bike и типов для магазина велосипедов, реализация CommandLineView на Groovy не имеет помех
при явном написании методов getter или setter для свойства RentABike. Также в методе printAllBikes() снова
использовано преимущество мощных расширений Groovy для коллекций. Для печати каждого велосипеда в коллекции используется each.
Использование Spring для сборки
В предыдущих разделах было показано сравнение Groovy и Java при определении объектов для велосипеда, магазина велосипедов и представления магазина велосипедов. Сейчас самое время посмотреть, как можно собрать все приложение и использовать представление командной строки для отображения списка велосипедов в инвентарном списке с помощью Spring.
При программировании на Java после определения интерфейса можно с помощью шаблона мастера делегировать ответственность за создание фактической реализации класса мастеру объектов.
Использование Spring в качестве мастера в значительной мере способствует сокращению помех в коде и часто хорошо работает и для Groovy, и
для Java. В финальных примерах кода Spring
будет отвечать за создание экземпляра типа CommandLineView и в Java, и в Groovy.
В листинге 9 среда Spring настроена для создания и внедрения
реализации магазина велосипедов ArrayList в CommandLineView перед возвращением экземпляра CommandLineView. Это означает, что нет необходимости ссылаться на реализацию ArrayList в версиях представления командной строки
ни на Java, ни на Groovy в листингах 7 и 8.
В Java-версии вставляемый класс
почти всегда будет ссылаться на интерфейс, а не на реализацию. В Groovy использование ключевого слова def
позволяет использовать преимущества "утиной типизации". В любом случае, сейчас среда Spring настроена для предоставления
экземпляра представления магазина велосипедов, полностью сконфигурированного с экземпляром типа магазина велосипедов!
Листинг 9. Файл конфигурации Spring
<beans>
<bean id="rentaBike" class="ArrayListRentABike">
<property name="storeName"><value>"Bruce's Bikes (spring bean)"</value></property>
</bean>
<bean id="commandLineView" class="CommandLineView">
<property name="rentaBike"><ref bean="rentaBike"/></property>
</bean>
</beans>
|
В листингах 10 и 11,
сборочные типы магазина велосипедов создают экземпляр ClassPathXmlApplicationContext среды Spring с файлом конфигурации из Листинга 9, а затем запрашивают экземпляр представления магазина велосипедов:
10. Java-версия вызова Spring для создания представления магазина велосипедов
Листинг
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class RentABikeAssembler {
public static final void main(String[] args) {
// Create a Spring application context object
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext("RentABike-context.xml");
// retrieve an object from Spring and cast to a specific type
CommandLineView clv = (CommandLineView)ctx.getBean("commandLineView");
clv.printAllBikes();
}
}
|
Заметим, что в Java-версии в листинге 10 вызов Spring для запроса экземпляра представления командной строки требует приведения к типу объекта, который поддерживает метод printAllBikes(). В этом случае выданный средой Spring
объект передается в CommandLineView.
Для Groovy и его поддержки "утиной типизации"
приведение типов не нужно. Нужно лишь убедиться, что класс, который вернула среда Spring, может ответить
на соответствующий вызов метода, то есть printAllBikes().
Листинг 11. Groovy-версия сборки Spring
import org.springframework.context.support.ClassPathXmlApplicationContext
class RentABikeAssembler {
public static final void main(String[] args) {
// Create a Spring application context object
def ctx = new ClassPathXmlApplicationContext("RentABike-context.xml")
//Ask Spring for an instance of CommandLineView, with a
//Bike store implementation set by Spring
def clv = ctx.getBean("commandLineView")
//duck typing again
clv.printAllBikes()
}
}
|
Как можно видеть в листинге 11, "утиная типизация" в Groovy помогает сократить избыточность кода не только путем исключения необходимости объявлять интерфейсы для поддержки автоконфигурации объекта средой Spring. Она также упрощает использование полностью настроенного bean-компонента, который вернул контейнер Spring.
Надеюсь, демонстрация мощности языка Groovy и того, как он может изменить исходный код, была удачной. В противоположность приведенным выше примерам на Java, код на Groovy меньше и легче для понимания. Кто угодно, начиная с опытных Java-архитекторов и заканчивая программистами на отличных от Java языках, могут легко уловить назначение кода. С помощью Groovy и его поддержки динамической типизации можно также уменьшить количество файлов, которыми нужно управлять. В конечном итоге, использование Groovy значительно сокращает количество помех, сопровождающих типичные программы на Java. И это звучит здорово!
Научиться
- Оригинал статьи Practically Groovy: Reduce code noise with Groovy;
- "Groovy на практике: быстрое модульное тестирование Java-кода с помощью Groovy" (Эндрю Гловер (Andrew Glover), сайт developerWorks, ноябрь 2004 г.). Написание модульных тестов на Groovy представляет собой прекрасный способ знакомства с этим языком;
- "Groovy на практике: используйте Groovy в Java-приложениях" (Эндрю Гловер (Andrew Glover), сайт developerWorks, май 2005 г.). Groovy легко можно использовать в Java-приложениях;
- "Groovy на практике: гладкие операторы" (Эндрю Гловер (Andrew Glover), сайт developerWorks, октябрь 2005 г.). Пристальный взгляд на перезагрузку операторов с помощью Groovy;
- "Введение в Spring с использованием Swing" (Чэд Вулли (Chad Woolley), сайт developerWorks, ноябрь 2005 г.). Учебник по инверсии управления с использованием Spring;
- "Введение в Spring 2 и JPA" (Синг Ли (Sing Li), сайт developerWorks, август 2006 г.). Пошаговое создание Web-приложения "с нуля" с помощью среды Spring 2;
- Spring: записная книжка разработчика (Брюс Тейт и Джастин Гетланд (Bruce A. Tate, Justin Gehtland), O'Reilly, 2005 г.). Краткое введение в различные способы использования Spring для улучшения приложения.
- "Шаблоны в действии" (Джохен Кребс (Jochen Krebs), сайт developerWorks, май 2006 г.). Для разработчиков программ, незнакомых с шаблонами и их различными отношениями, эта статья предоставит общий обзор использования шаблонов в контексте реального проекта по разработке программного обеспечения;
-
Домашняя страница Groovy: Многочисленные
ресурсы для новичков в Groovy: часто задаваемые вопросы, канал чата,
список почтовой рассылки, википедия, а также подробности о миграции на
обновленный синтаксис для доступа к свойствам;
-
Серия статей Groovy на практике : советы и приемы для программистов на Groovy;
- Зона технологии Java на сайте developerWorks: сотни статей по каждому аспекту программирования на Java.
Получить продукты и технологии
-
Страница загрузки Groovy: загрузите Groovy с Web-узла проекта;
- Среда Spring: загрузите самую последнюю версию Spring с Web-узла проекта.
Обсудить
- Примите участие в форуме по
технологии Java;
-
Блоги developerWorks: присоединяйтесь к сообществу developerWorks.
Скотт Хики (Scott Hickey) является консультантом по Java и работает в компании Bass and Associates. Он разрабатывает программное обеспечение уже свыше 20 лет, а с Java работает с 1998 года. Он также является ведущим разработчиком проекта Groovy Eclipse Plugin.