Статьи

Метапрограммирование с Groovy I

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

Вступление

Вы, наверное, уже знаете, что Groovy — это язык для JVM, который имеет отличную интеграцию с языком и платформой Java, вы даже можете написать Groovy, используя синтаксис Java (см. Из серии Java в Groovy за несколько простых шагов ), и что среди прочего он позволяет вам написать некоторые дурацкие вещи, такие как следующие

// look ma, I'm calling a method on an integer!
3.times { println "hello!" }

List aList = []
// what's that wacky operator doing to aList ?
aList << "a string"
assert aList[0] == "a string"

Да, Groovy позволяет вам делать дурацкие вещи, такие как вызов методов для целочисленной и перегрузки операторов. Но как это сделать? В прошлый раз, когда я проверял, java.lang.Integer объявлен окончательным, как тогда можно добавлять методы в экземпляры Integer? Что ж, получается, что в Groovy есть что-то, называемое Meta Object Protocol, которое позволяет то, что вы видели, и даже больше. Каждый объект в Groovy (независимо от того, происходит ли он от класса Java или класса Groovy) имеет MetaClass, отвечающий за хранение информации о классе объекта. Каждый раз, когда вы вызываете метод объекта, механизм диспетчеризации Groovy направляет вызов через MetaClass, связанный с объектом, поэтому, если вы можете каким-либо образом изменить MetaClass, вы можете изменить поведение объекта во время выполнения, и для этого Groovy предлагает пара вариантов, давайте рассмотрим первый: категории.

Категоризировать меня это

Категории на самом деле очень простые вещи, они могут быть любым классом, который придерживается следующих соглашений

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

Хорошо, давайте следовать правилам и посмотрим, что получится

class Pouncer {
static pounces( Integer self ){
(0..<self).inject("") { s, n ->
s += "boing! "
}
}
}

use( Pouncer ) {
assert 3.pounces() == "boing! boing! boing! "
}

Волшебная часть происходит в строках с 9 по 11, где мы используем метод, доступный для всех объектов use () . Вы можете использовать любой класс (или список классов в этом отношении) в качестве параметра для use () , суть в том, что все методы в этом классе являются кандидатами на методы категоризации, если они следуют вышеупомянутым правилам, в нашем случае Метод pounces () будет добавлен в Integer, потому что он статический, а первый параметр имеет тип Integer. Вызов pounce () для целого числа дает строку с таким количеством «boing!» строки объединяются в одну строку, которая начинается как пустая строка. В набрасывается () методы будут доступны только в рамках использования () блок, если вы попытаетесь вызвать его до или после блока, вы получите исключение.

О, да, строки с 3 по 5, вот где происходит другая часть магии. Если вы не знакомы с некоторыми типами данных и операторами Groovy (0 .. <self), это исключительный диапазон от целого 0 до целого (self — 1). Диапазоны имеют много общих методов со списками, одним из которых является inject () , который принимает два параметра: начальное значение и закрытие. Его поведение — перебирать все значения диапазона, используя начальное значение в качестве начального числа и применяя замыкание к каждому элементу; вы обычно возвращаете обновленное значение, основанное на засеянном, для обработки следующим элементом в следующей итерации.

Вот еще один пример обычного класса, используемого в качестве категории. Проект Apache Commons содержит множество полезных небольших проектов, одним из которых является commons-lang. У Commons-lang есть несколько служебных классов, таких как ClassUtils и WordUtils, которые предоставляют методы, которые, по мнению многих, должны быть в JDK. ( Примечание : проект commons был запущен до появления jdk 1.4, некоторые методы перешли в более поздние версии JDK). Эти служебные классы представляют собой набор статических методов, что делает их идеальными для категоризации.

import org.apache.commons.lang.ClassUtils
import org.apache.commons.lang.StringUtils
import org.apache.commons.lang.WordUtils

use( ClassUtils, StringUtils, WordUtils ) {
assert "java.lang.Class".packageName == "java.lang"
assert "java.lang.Class".shortClassName == "Class"
assert Map.Entry.isInnerClass()

assert "CamelCase".swapCase() == "cAMELcASE"
assert "ALL CAPS".uncapitalize() == "aLL cAPS"
assert "GROOVY JAVA".initials() == "GJ"

assert "FOO_BAR_BAZ".split("_").collect { word ->
word.toLowerCase().capitalize()
}.join("") == "FooBarBaz"
}

 

Поймать

Категории добавляют методы к классам в течение ограниченного периода времени, используя переменную ThreadLocal для хранения новых методов, поэтому использование их в многопоточной среде не является хорошим выбором, вам потребуется более длительное и производительное решение. Вы также требуете вызова метода use () , нет прозрачного способа работы с категориями без него, если только вы не заключаете код в оболочку таким образом, что, когда ваше приложение запускается, оно запускается внутри блока категории (что в некоторой степени работает в groovlets ).

Вы, вероятно, задаетесь вопросом, если у диапазонов много общих методов со списками, они, вероятно, используют некоторые новые, такие как inject () , и, более того, я ранее говорил, что все объекты имеют метод use () . Эти методы принадлежат тому, что называется GDK . Эти методы также доступны через метаклассы, на самом деле вы можете даже переопределить большинство из них, используя категории, если необходимо, различие заключается в том, что эти методы добавляются другим способом, когда Groovy запускается во время выполнения, так что они всегда доступны.

Вывод

Категории — это простой способ начать эксперименты метапрограммирования с Groovy, в следующей части мы рассмотрим ExpandoMetaClass, более мощный и настраиваемый способ улучшения ваших классов во время выполнения.