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