Недавно я купил книгу Мартина Фаулера о предметно-ориентированных языках. Это отличная книга, показывающая различные шаблоны, которые можно использовать при написании DSL. Автор иллюстрирует все шаблоны, используя три языка: Java, C # и Ruby, поскольку существует группа шаблонов, которые применимы только к языкам со статической типизацией, и есть некоторые шаблоны, которые требуют динамических возможностей для их реализации.
Одна из самых приятных особенностей Groovy — это дополнительная типизация, которая позволяет нам использовать оба типа шаблонов. Чтобы проиллюстрировать, как легко писать DSL в Groovy, я собираюсь написать серию статей, показывающих различные шаблоны.
Хотя может быть полезно взглянуть на некоторые примеры в Groovy, чтобы получить полное представление о шаблонах, лучше купить книгу Фаулера.
Давайте посмотрим на чрезвычайно простой DSL, позволяющий мне указать, кто за кем следует в Twitter. Во-первых, я создаю семантическую модель для нашего примера:
class User {
private String name
final followers = [] as Set
final following = [] as Set
User(String name){
this.name = name
}
void addFollower(User u){
followers << u
u.following << this
}
boolean equals(obj){
getClass() == obj?.getClass() && name == obj?.name
}
}
Семантическая модель содержит всю необходимую логику и должна использоваться другими частями нашей системы. DSL — это дополнительный уровень поверх семантической модели. Это помогает заполнить его, используя более читаемый синтаксис.
Чтобы заполнить нашу модель напрямую, мы можем использовать такой фрагмент кода:
def jim = new User(‘jim’)
def tom = new User(‘tom’)
jim.addFollower tom
Я бы хотел, чтобы мой DSL выглядел так:
List<User> users = FollowersGraphBuilder.build {
users 'jim', 'tom', 'jane'
user('jim').follows 'tom'
user('tom').follows 'jane'
}
Давайте начнем с метода сборки:
class FollowersGraphBuilder {
static List<User> build (Closure c){
def builder = new FollowersGraphBuilder()
c.delegate = builder
c()
builder.createdUsers
}
}
Здесь мы используем объектную область видимости, чтобы предоставить все необходимые методы, такие как пользователи и пользователи, для нашего закрытия Когда я указываю свойство делегата, все вызовы метода ‘users’ будут делегированы нашему сборщику. Это позволяет мне добиться поведения, подобного instance_eval в ruby.
Метод users просто создает группу пользователей и заполняет карту, чтобы ссылаться на пользователя по его имени. Этот шаблон называется таблицей символов и широко используется. Карта является самой простой реализацией таблицы символов, но здесь она отлично работает.
class FollowersGraphBuilder {
private users = [:]
def users(String... userNames){
userNames.each {name->
users[name] = new User(name)
}
}
}
Пока наш строитель может создавать новых пользователей, но он все еще не знает, как определить отношения между ними. Чтобы сделать это возможным, я собираюсь создать еще один конструктор: UserFollowingBuilder и добавить метод в FollowersGraphBuilder, чтобы создать его.
class FollowersGraphBuilder {
def user(String name){
new UserFollowingBuilder(parent: this, user: getUserByName(name))
}
def getUserByName(String name){
assert users.containsKey(name), "Invalid user name $name"
users[name]
}
}
class UserFollowingBuilder {
FollowersGraphBuilder parent
User user
def follows(String name){
parent.getUserByName(name).addFollower user
this
}
}
Теперь я могу написать эту строку кода, чтобы указать, что Джон следует за Питером и Бобом.
user('john').follows('piter').follows('bob')
Шаблон, который был использован здесь, называется методом цепочки. Обычно после одного из вызовов тип приемника был изменен. Это происходит здесь, когда получатель был изменен с FollowersGraphBuilder на UserFollowingBuilder. Чтобы это выглядело немного лучше, я собираюсь добавить метод синтаксического сахара и:
class UserFollowingBuilder {
def and(String name){
follows name
}
}
Ну не так уж и плохо
user('john').follows('piter').and('bob')
FollowersGraphBuilder и UserFollowingBuilder являются двумя примерами шаблона Expression Builder. Их цель — обеспечить свободный API, который в сущности является сущностью DSL.
В заключение:
class FollowersGraphBuilder {
List<User> getCreatedUsers(){
users.values().toList()
}
}
Вот и все, я только что реализовал простой DSL с использованием самых разных шаблонов:
- Семантическая модель — это наш пользовательский класс; в более сложных примерах реализация семантической модели — самая сложная часть.
- Объектная область — предоставить все необходимые методы; может быть достигнуто путем наследования или путем указания свойства делегата.
- Таблица символов — для адресации пользователей по их именам.
- Expression Builder — это сущность DSL, так как он добавляет желаемую гибкость нашему API.
- Цепочка методов — для пользователя (‘john’). Follow (‘piter’). И (‘bob’)
В следующий раз я покажу вам, как устранить все имеющиеся у нас шумы и преобразовать пользователя (‘john’). Follow (‘piter’). И (‘bob’) в john.follows (piter) .and (bob).
С http://vsavkin.tumblr.com/post/3146590777/series-dsls-in-groovy-part-1