Статьи

Динамическое метапрограммирование, повторное использование на следующий уровень

Эта статья о том, как перенести повторное использование на следующий уровень с помощью возможностей динамического метапрограммирования Groovy.

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

В отличие от Java, с Groovy легко перейти на следующий уровень повторного использования, используя его функции динамического метапрограммирования.

Скажем, например, у нас был класс Computer, который использовал шаблон проектирования Observer / Observable, чтобы уведомить заинтересованные третьи стороны о том, что произошло событие аренды. Мы покажем, как повторно использовать один и тот же код, но изменить событие, которое выдается. Этот класс Computer использует / реализует события Java следующим образом:

RentableEvent, расширяющий java.util.EventObject

 
public class RentableEvent extends EventObject{
RentableEvent (Computer source) {
super(source)
}
}

RentListener, который расширяет java.util.EventListener

 
interface RentListener extends EventListener {
void rented(RentableEvent re)
void returned(RentableEvent re)
void overdue(RentableEvent re)
}

Groovy еще нет вкусностей. Выше приведена стандартная Java около 1997 года. Слушатель определяет три события: когда компьютер арендуется, когда компьютер возвращается и когда компьютер был возвращен, но просрочен.

Тогда в классе Computer у нас будет следующий код для запуска событий:

Компьютерный класс, который можно сдавать в аренду

 

public class Computer implements Serializable, Rentable {
...
/** rentableListeners List of RentListener's property that holds listeners. */
List rentableListeners = []


/** Rent the computer. */
void rent(int days=5) {
/* Throws an IllegalStateException if the computer was already rented. */
if (rented) {
throw new IllegalStateException("You can't rent a computer twice")
}
/* Set the days property, startRent and the rented property accordingly. */
this.days = days
rented = true
startRent = Calendar.getInstance()
/* Fire the rent event. */
fireRentEvent()
}

void endRent(Calendar returnedOn=Calendar.getInstance()){
/* Determine if the due date is before the returned date. */
Date dueDate = startRent.time + days
Date actualReturnDate = returnedOn.time

/* Fire the appropiate event based on the overdue status. */
if (dueDate.before (actualReturnDate)) {
fireOverdueEvent()
} else {
fireReturnedEvent()
}
rented = false
}

/* Fire rented event. */
public void fireRentedEvent() {
RentableEvent event = new RentableEvent(this)
rentableListeners.each {RentListener listener -> listener.rented(event) }
}

/* Fire overdue event. */
public void fireOverdueEvent() {
RentableEvent event = new RentableEvent(this)
rentableListeners.each {RentListener listener -> listener.overdue(event) }
}

/* Fire returned event. */
public void fireReturnedEvent() {
RentableEvent event = new RentableEvent(this)
rentableListeners.each {RentListener listener -> listener.returned(event) }
}

void addRentableListener(RentListener listener) {
rentableListeners << listener
}

void removeRentableListener(RentListener listener) {
rentableListeners << listener
}

}

Обратите внимание, что вышеприведенные события запускают три типа событий следующим образом: fireRentedEvent, fireOverdueEvent и fireReturnedEvent.

Для новичков в Groovy для запуска события в приведенном выше коде используется метод GDK Groovy для коллекций, вызываемых каждым, который будет вызывать блок (называемый замыканием) для каждой итерации (каждого элемента) в коллекции слушателей. Затем внутри блока (снова называемого замыканием) мы вызываем соответствующий метод, чтобы уведомить слушателя о событии. GDK расшифровывается как Groovy Development Kit и представляет собой способ расширения базовых классов Java с помощью возможностей языка Groovy.

Одна вещь, которую вы заметите в fireRentedEvent, fireOverdueEvent и fireReturnedEvent, это то, что они почти одинаковы. Таким образом, проблема в том, что у нас есть три метода, которые почти одинаковы. По сути, они нарушают принцип «Не повторяй себя» (СУХОЙ). Давайте решим эту проблему, используя возможности метапрограммирования Groovy.

В Groovy легко сделать метод слушателя, который будет вызываться параметром, используя invokeMethod.

Использование invokeMethod для получения нового уровня повторного использования

 

/* Fire rented event. */
public void fireRentEvent(String methodName="rented") {
RentableEvent event = new RentableEvent(this)
rentableListeners.each {RentListener listener ->
/* Use invokeMethod to invoke method. */
listener.invokeMethod(methodName, event)
}
}

Теперь какой метод вызывается, определяется methodName, и мы избавились от трех почти идентичных методов и заменили его одним методом. Представьте, что у нас было 10 мероприятий, и вы можете легко увидеть, что экономия быстро увеличивается.

Теперь эта статья поучительна; поэтому пример, который он использует, немного надуман. Однако бывают случаи, когда у вас есть очень похожие блоки кода, которые делают почти то же самое, за исключением вызываемого метода, и да, я знаю, что вы могли бы переписать вышеупомянутое намного меньше следующим образом:

 
/* Fire returned event. */
public void fireReturnedEvent() {
rentableListeners.each {it.returned([this] as RentableEvent) }
}

И вышеупомянутое является действительным пунктом. Тем не менее, суть статьи заключается в том, как добиться повторного использования, когда у вас есть очень похожие блоки кода (в данном случае методы), которые выполняют почти одно и то же, за исключением вызова различных методов. В Java становится немного сложнее получить повторное использование. Вы заканчиваете тем, что пишете внутренние классы, чтобы передать, какой метод вызывается или дублировать код, или если вы имеете дело с иерархией объектов, вы можете реализовать шаблон проектирования посетителя. В Groovy вы можете просто использовать invokeMethod в качестве опции.

Это поднимает другой вопрос. В Groovy есть несколько способов легко добиться такого повторного использования. Мы могли бы передать ссылку на метод. Мы могли бы использовать закрытие. В двух коротких последующих статьях мы рассмотрим оба этих подхода.

Метод invokeMethod добавляется ко всем экземплярам. Он добавляется в java.lang.Object. Сигнатура метода invokeMethod принимает два аргумента: имя метода, который вы хотите вызвать, и затем n аргументов, используя переменные аргументы стиля Java 5. Он очень прост в использовании.

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

Кодификация строк путем добавления вспомогательных методов

 

/* Fire overdue event. */
public void fireOverdueEvent() {
fireRentEvent("overdue")
}

/* Fire returned event. */
public void fireReturnedEvent() {
fireRentEvent("returned")
}

Более идиоматический подход к динамическому вызову метода

Существует более идиоматический подход, чем использование invokeMethod. В Groovy вы можете использовать instance. «Foo» () для выполнения метода с именем foo. Используя этот подход, вы можете сделать следующее:

rentableListeners.each {RentListener listener ->
/* Call method dynamically by name. */
listener."$methodName" event
}

Преимущество подхода invokeMethod по сравнению с идиоматическим подходом состоит в том, что он будет использовать на один уровень косвенности, так как в идиоматическом подходе используется GString («$ methodName»), который должен быть преобразован в имя метода. (Отдельное спасибо Andres Almiray и Bloid за этот перл мудрости и рассуждений.) Идиоматический подход, вероятно, самый удачный способ сделать это. Для новичков в Groovy следует отметить, что вы можете вызывать методы без скобок.

Вывод

В этой короткой статье мы показали, как вы можете подтолкнуть повторное использование на новый уровень и оставаться чрезвычайно СУХИМ, используя поддержку динамического метапрограммирования Groovy. В последующих статьях мы покажем, как добиться того же, используя замыкания Groovy, а затем ссылки на методы.

Обратите внимание, что эта статья не носит предписывающий характер. Это не говорит о том, что вы должны использовать invokeMethod, просто, что invokeMethod является жизнеспособным вариантом для достижения другого уровня повторного использования.

Если вам понравилась эта статья и вы хотите узнать больше о Groovy, ознакомьтесь с курсом ArcMind Groovy: обучение Groovy . Также я планирую добавить поддержку языка Groovy к проекту, который скоро будет переименован в Crank . Вы можете найти необработанные версии этой статьи здесь. Динамическое метапрограммирование, повторное использование на следующем уровне и другие статьи, находящиеся в стадии разработки .