Статьи

Экспериментальный DslHelper для более краткого кода поддержки DSL

В продолжение предыдущей статьи о DSL я предлагаю DslHelper, экспериментальный класс для дальнейшего упрощения кода поддержки DSL.

Рассмотрим этот DSL:

architecture {
    rules {
        "beans-web" {

        }
    }
}

Для реализации "beans-web"()вызова метода (строки с 3 по 5) мне нужно написать этот код:

class RulesDelegate {
    private Configuration configuration
    RulesDelegate(Configuration configuration) {
        this.configuration = configuration
    }
    def methodMissing(String name, Object args) {
        if (args.length == 1) {
            if (args[0] instanceof Closure) {
                Rule rule = new Rule(name)

                args[0].delegate = new RuleDelegate(rule)
                args[0].resolveStrategy = Closure.DELEGATE_FIRST

                args[0]()

                this.configuration.addRule rule
            } else {
                throw new MissingMethodException(name, this.class, args as Object[])
            }
        } else {
            throw new MissingMethodException(name, this.class, args as Object[])
        }
    }
}

В methodMissing()метод (строки 6 до 23) автоматически вызывается с помощью Groovy всякий раз , когда метод не найден (отсутствует) на объекте. Таким образом, добавляя missingMethod()к любому классу, вы можете перехватывать все вызовы несуществующих методов, и это именно то, что вам нужно для реализации компоновщика.

Однако methodMissing()реализация менее привлекательна. Я хочу выполнить свой код, только если я получу один Closureобъект в качестве аргументов. Но так как сигнатура метода methodMissing()определяется Groovy, я должен проверить количество аргументов и набрать сам.

Если бы я только мог реализовать missingMethod()это так:

def methodMissing(String name, Closure cl) {
    Rule rule = new Rule(name)

    cl.delegate = new RuleDelegate(rule)
    cl.resolveStrategy = Closure.DELEGATE_FIRST

    cl()

    this.configuration.addRule rule
}

Это не только меньше кода, но и более читаемый. Увы, Groovy не будет вызывать этот метод. Поэтому нам нужно помочь Groovy 🙂

Enter DslHelper, экспериментальный класс, который я написал, который поддерживает строго типизированные methodMissing()методы!

def methodMissing(String name, Object args) {
    return DslHelper.methodMissing(this, name, args)
}
def methodMissing(String name, Closure cl) {
    Rule rule = new Rule(name)

    cl.delegate = new RuleDelegate(rule)
    cl.resolveStrategy = Closure.DELEGATE_FIRST

    cl()

    this.configuration.addRule rule
}

Позвонив DslHelper.methodMissing()в methodMissing(String,Object)методе делегировать вызов methodMissing(String,Closure). Для более простых вещей я также могу расширить DslHelper:

class RulesDelegate extends DslHelper {
    private Configuration configuration
    RulesDelegate(Configuration configuration) {
        this.configuration = configuration
    }
    def methodMissing(String name, Closure cl) {
        Rule rule = new Rule(name)

        cl.delegate = new RuleDelegate(rule)
        cl.resolveStrategy = Closure.DELEGATE_FIRST

        cl()

        this.configuration.addRule rule
    }
}

DslHelperпредоставляет нестатический methodMissing(String,Object)метод, позволяющий сосредоточиться на синтаксисе DSL.

Вы можете переопределить methodMissing()столько раз, сколько захотите, DslHelperприложит все усилия, чтобы сопоставить аргументы с методом. Возьмем для примера этот DSL:

architecture {
    rules {
        "beans-web" {

        }

        "web-beans"(ignore: true) {

        }
    }
}

"web-beans"()Метод будет проходить два аргумента в methodMissing(): а Mapи А Closure(Groovy новообращённых именованные аргументы к Mapобъекту).

Вы можете продлить или позвонить DslHelperи сосредоточиться на methodMissing():

class RulesDelegate extends DslHelper {
    private Configuration configuration
    RulesDelegate(Configuration configuration) {
        this.configuration = configuration
    }
    def methodMissing(String name, Closure cl) {
        // implementation
    }
    def methodMissing(String name, Map params, Closure cl) {
        // implementation
    }
}

DslHelper также поможет вам с необязательными аргументами в вашем синтаксисе DSL:

architecture {
    rules {
        "beans-web" {

        }

        "web-beans"(ignore: true) {

        }

        "io-beans"(silent: true, "option1", "option2") {

        }
    }
}

Чтобы получить эти Stringобъекты, добавьте String[]массив в methodMissing()( Mapобъект должен стоять первым). DslHelperпередаст Stringзначения в виде String[]массива, даже если Stringпередано только одно значение.

class RulesDelegate extends DslHelper {
    private Configuration configuration
    RulesDelegate(Configuration configuration) {
        this.configuration = configuration
    }
    def methodMissing(String name, Closure cl) {
        // implementation
    }
    def methodMissing(String name, Map params, Closure cl) {
        // implementation
    }
    def methodMissing(String name, Map params, String[] options, Closure cl) {
        // implementation
    }
    def methodMissing(String name, String[] options, Closure cl) {
        // added for completeness
    }
}

DslHelper является экспериментальным, поэтому используйте его на свой страх и риск.

Удачного кодирования!