Хотя Интернет изобилует ресурсами, превозносящими бесчисленные возможности метапрограммирования Groovy, я еще не нашел ничего похожего на исчерпывающее руководство по «наилучшей практике» для фактического использования таких функций.
Помимо типичного предупреждения caveat о чрезмерном использовании, наиболее конкретный совет, который я прочитал, предлагает использовать категории в пользу расширения метаклассов, когда это возможно (что на самом деле является просто еще одним способом усиления старая идиома «ограниченной области»).
Здравого смысла было достаточно для моих тривиальных проектов, но я все больше беспокоюсь о том, чтобы строить из потенциально бедных / противоречивых прецедентов, когда я решаю более амбициозные задачи.
Таким образом, я был бы очень признателен за любые советы, ресурсы или конкретные примеры Groovy (или даже не зависящие от языка - мой по общему признанию краткий опыт работы с Ruby оставил меня таким же желанием) лучших методов метапрограммирования.
Чтобы прояснить тему, я приведу (очень) упрощенный проект Rational Number, который может использовать метапрограммирование несколькими различными способами:
@Immutable class Rational{
int num, den
Rational multiply(Integer v){
new Rational(num:num*v, den:den)
}
}
assert new Rational(num:1, den:2) * 3 == new Rational(num:3, den:2)
Тем не менее, попытка 3*new Rational(num:1, den:2)
, очевидно, приведет к исключению MissingMethodException.
Самым простым и, возможно, наименее хрупким способом добавления коммуникативного свойства был бы статический блок инициализатора в классе Rational:
static {
Integer.metaClass.multiply = {Rational fraction -> fraction*delegate}
}
...
assert 3*new Rational(num:1, den:2) == new Rational(num:3, den:2)
Но это, следовательно, глобально, и довольно жестко.
Более универсальный и, возможно, более организованный подход был бы с некоторой опциональной начальной загрузкой:
class BootStrap{
static void initialize(){
Integer.metaClass.multiply = {Rational fraction -> fraction*delegate}
}
}
Теперь у нас есть возможность включить функции, которые мы хотим иметь. Это, однако, может привести к всевозможным проблемам с зависимостями.
И еще есть категории .. Безопасно явно, но не совсем удобно:
@Category(Integer) class RationalCategory{
Rational multiply(Rational frac){
frac*this
}
}
use(RationalCategory){
assert 3*new Rational(num:1, den:2) == new Rational(num:3, den:2)
}
В общем, я нахожу, что я изменяю метаклассы, когда я добавляю новое поведение, но использую категории, когда я могу изменять существующее поведение. Например, переопределение оператора деления для создания дроби будет лучше всего заключено в категории. Таким образом, решения 1 или 2 будут «приемлемыми», поскольку я просто добавляю поведение к классу Integer, а не изменяю типичное использование.
Кто-нибудь согласен / не согласен с этим мнением? Или, может быть, знаете о какой-то превосходной методологии? (Я опускаю миксины здесь, я понимаю.)