Статьи

Серия: DSL в Groovy, часть: 1

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