Groovy на практике: сокращение кода с помощью Groovy

В данной статье описано, как Groovy позволяет сосредоточиться на важных аспектах создания кода

Лаконичный синтаксис Groovy освобождает разработчиков от типичных конструкций Java,™ которые необходимы для компиляции кода, но не способствуют выражению того, что именно программа реально пытается делать. Данную статью, которая возобновляет серию Groovy на практике, написал приглашенный автор Дж. Скотт Хики, разработчик на Groovy. Он приведет серию сравнений обычного кода на Java и аналогичного кода на Groovy, а также продемонстрирует, как этот прекрасный язык освобождает разработчика и позволяет ему сосредоточиться на важных аспектах создания кода.

Скотт Хики, консультант, Bass and Associates, Inc.

Скотт Хики (Scott Hickey) является консультантом по Java и работает в компании Bass and Associates. Он разрабатывает программное обеспечение уже свыше 20 лет, а с Java работает с 1998 года. Он также является ведущим разработчиком проекта Groovy Eclipse Plugin.



19.09.2006

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

В качестве примера сокращения лишнего кода простого старого Java-приложения с помощью Groovy будет использован код из книги Брюса Тейта (Bruce Tate) и Джастина Гетланда (Justin Ghetland) Spring: записная книжка разработчика (Ресурсы), в которой представлена инверсия управления с помощью Spring. По мере рассмотрения каждого примера кода на Java будет делаться сравнение с соответствующим функционально эквивалентным исходным кодом на Groovy. Думаю, что довольно быстро станет понятно, насколько Groovy делает код приложения яснее и сокращает различные аспекты программирования на Java, которые излишни и не обязательно отображают поведение приложения.

Смысл Groovy

В первой главе книги Брюса и Джастина они создают простое приложение для магазина велосипедов, в котором есть четыре различных класса. Сначала рассмотрим простой класс JavaBean под названием Bike, который представляет собой велосипед на складе. Затем идет тип магазина велосипедов под названием RentABike, который содержит коллекцию велосипедов (Bike). Есть также класс для отображения списка велосипедов с удачным названием CommandLineView, который зависит от типа RentABike. И, наконец, есть класс, отвечающий за сборку частей для создания работающего приложения. Для полной конфигурации класса CommandLineView с помощью типа RentABike без ручного написания кода используется Spring.

Пусть JavaBean помолчит!

Класс, который в листинге 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

Последняя версия языка Groovy, JSR-06, упростила в нем семантику свойств. До выхода этой версии свойства определялись с помощью аннотации @Property, предшествующей объявлению переменной. Однако такая нотация больше не нужна. С версии JSR-06 свойство private с public-методами getter и setter будет сгенерировано просто путем указания типа и имени переменной. Вместо конкретного типа также можно использовать ключевое слово def.

Более того, с помощью Groovy функциональность класса по умолчанию выражается короче по сравнению с тем, что необходимо писать явно в обычном коде на Java (как в листинге 1). Когда есть необходимость сделать нечто особенное с конструктором либо с методом getter или setter, Groovy демонстрирует неоспоримые преимущества, поскольку смысл поведения программы становится немедленно понятен. Надо просто взглянуть на код! Например, в листинге 2 легко увидеть, что метод setCost() собирается масштабировать свойство cost до трех десятичных позиций.

Сравните этот скромный по размерам код на Groovy с исходным кодом на Java в листинге 1. Можно ли во время первого прочтения листинга сразу заметить, что метод setCost() обладает специальной встроенной функциональностью? Если не искать специально, то это легко упустить!

Тест на звучание Groovy

Контрольный пример для класса 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 для задания свойств объекта исключает необходимость в этих излишних и утомительных конструкторах с различными комбинациями аргументов, которые нужны лишь для инициализации переменных.

Groovy и IDE

Такие IDE, как Eclipse, облегчают автоматическое создание методов getter и setter. Эти средства также облегчают извлечение интерфейсов из конкретных классов для обеспечения рефакторинга. Можно поспорить, однако, что код читают чаще, чем пишут. К сожалению, чтобы разобраться в том, что программа на самом деле делает, разработчики по-прежнему вынуждены погружаться в сгенерированный код и исходные файлы. Учитывая высокое качество доступных инструментальных средств Java, величина экономии строк при создании исходного кода на Groovy -- это спорный вопрос. Но при беглом взгляде код на Groovy короче, удобнее для восприятия и менее сложен. На практике это является большим преимуществом при работе с приложениями, состоящими из сотен классов и тысяч строк кода.

Снижение сложности

В предыдущем разделе GroovyBean-компонент Bike использовал преимущества семантики свойств и конструкторов Groovy для удаления избыточных строк из исходного кода. В данном разделе Groovy-версия учета велосипедов будет также использовать дополнительные преимущества для сокращения избыточности кода. Это "утиная типизация" (duck-typing) для полиморфизма, расширения классов-коллекций и перегрузка операторов.

Groovy и полиморфизм

В 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

Использование замыканий с такими методами, как each и find, упрощает многие часто используемые задачи по организации коллекций данных, например, циклы и поиск. Сравните Java-версию для getBike()в листинге 5 с кодом на Groovy в листинге 6. В Groovy сразу ясно, что мы ищем объект Bike по его серийному номеру. В Java-версии определение Iterator и нахождение следующего элемента в списке -- это помехи, затрудняющие понимание того, что же на самом деле делает приложение, т.е. ищет велосипед.

Прозрачный код

"Утиная типизация" и семантика свойств в 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

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

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java, Open source
ArticleID=174006
ArticleTitle=Groovy на практике: сокращение кода с помощью Groovy
publish-date=09192006