Практически Groovy : MOP и мини-языки

Groovy выводит протокол метаобъектов из лабораторий в приложения

Прильните к земле и послушайте – приближается MOP! Познакомьтесь с протоколом метаобъектов Meta Object Protocol - новому (хорошо забытому старому) подходу к созданию приложений, языков и приложений как языков.

Эндрю Гловер, президент компании, Stelligent Incorporated

Эндрю Гловер является президентом компании Stelligent Incorporated , которая помогает другим фирмам решать проблемы качества программного обеспечения. Для этого используются эффективные стратегии тестирования и технологии непрерывной интеграции, которые позволяют коллективам разработчиков постоянно контролировать качество кода, начиная с ранних стадий разработки. Просмотрите блог Энди , там можно найти список его публикаций.



06.02.2008

В недавнем интервью руководитель проекта Groovy Гийом Лафорж (Guillaume Laforge) сказал, что его любимая функция Groovy - это реализация протокола метаобъектов Meta Object Protocol (MOP). Этот протокол позволяет объектам во время работы программы, при передаче им методов или сообщений, делать выбор, определяющий их состояние и алгоритм работы. Описание на домашней странице проектов PARC Software Design Area (см. раздел Ресурсы):

Подход протокола метаобъектов ... базируется на той идее, что кто-нибудь может и должен открыть языки, предоставив пользователям возможность подгонки архитектуры и реализации под их нужды. Другими словами, поощряется участие пользователей в процессе разработки языка.

Определенно, такая широта мышления открывает замечательные возможности для создания более умных приложений и даже языков. В этом выпуске колонки я покажу вам, как MOP реализован в Groovy, после чего на рабочем примере познакомлю вас с одним из наиболее поразительных способов его практического применения: приложения-словаря, которое работает как мини-язык!

Исходный код примера приложения-словаря можно найти в разделе Материалы для загрузки. Ссылку для загрузки DbUnit, который понадобится вам в этом примере, можно найти в разделе Ресурсы.

Об этой серии руководств

Чтобы применять какой-либо инструмент в практике разработки, необходимо знать, как им пользоваться и когда его лучше отложить в сторону. Языки сценариев (или динамические языки) могут быть чрезвычайно полезным помощником, но только если применять их правильно и в подходящих ситуациях. В связи с этим в серии статей Practically Groovy мы рассматриваем практические применения Groovy и показываем, когда и как его можно успешно использовать.

Волшебный MOP

Протокол метаобъектов реализован не только в Groovy, равно как и не был придуман создателями Groovy. На самом деле можно проследить его родственные связи с LISP и с рядом людей, стоящих за аспект-ориентированным программированием. Учитывая эти родственные связи, неудивительно, что космополитичные создатели Groovy использовали MOP.

Реализация MOP в Groovy возможна потому, что каждый объект в стране Groovy косвенно реализует объект groovy.lang.GroovyObject, определяющий два метода invokeMethod() и getProperty(). Если в процессе работы программы отправляется сообщение объекту, не существующему как свойство или метод, определенный в классе или его иерархии, вызывается getProperty() или invokeMethod().

В листинге 1 я определил класс Groovy MOPHandler, в котором реализованы методы invokeMethod() и getProperty(). Создав экземпляр MOPHandler, я могу вызвать любое число методов или свойств и посмотреть, как выводятся сообщения, поясняющие их вызов.

Листинг 1. MOP справляется с вызовом
class MOPHandler {	

  def invokeMethod(String method, Object params) { 	
    println "MOPHandler was asked to invoke ${method}"
    if(params != null){
	 params.each{ println "\twith parameter ${it}" }
    }
  }

  def getProperty(String property){
     println "MOPHandler was asked for property ${property}"
  }  
}
def hndler = new MOPHandler()
hndler.helloWorld()
hndler.createUser("Joe", 18, new Date())
hndler.name

Запустив код, приведенный в листинге 1, вы получите результат, показанный в листинге 2.

Листинг 2. Верится с трудом?
aglover@glove-ubutu:~/projects/groovy-mop$ groovy 
  ./src/groovy/com/vanward/groovy/MOPHandler1.groovy
MOPHandler was asked to invoke helloWorld
MOPHandler was asked to invoke createUser
        with parameter Joe
        with parameter 18
        with parameter Sun Sep 04 10:32:22 EDT 2005
MOPHandler was asked for property name

Хитро, не так ли? MOP отлично работает как сеть, вылавливающая ошибочно переданные сообщения, но это не самая его интересная функция. Достоинства MOP проявляются тогда, когда вы используете его для создания разумных объектов, способных интеллектуально отвечать на любые переданные им сообщения в общей форме.


Сделай мне мини-язык

Одной из интересных вещей, которые вы можете сделать с MOP - создавать псевдо-проблемно-ориентированные языки, или мини-языки. Это уникальные языки, нацеленные на решение определенных задач. В отличие от обычных распространенных языков, к числу которых можно отнести Java, C# и даже Groovy, которые рассматриваются как общие языки, применимые к любой задаче, мини-языки заполняют определенные ниши. Нужен пример? Посмотрите на Unix с его почтенными командными процессорами, например, Bash.

Просто ради забавы - и для того, чтобы вы действительно могли ощутить возможности MOP - оставшуюся часть статьи я посвящу созданию приложения-словаря, которое само фактически будет мини-языком. Мое приложение будет предоставлять интерфейс для запросов к словарю. Оно позволит пользователю создавать новые словарные статьи, получать определение и синонимы для заданного слова, запрашивать часть речи этого слова, а также удалять слова. Краткое описание этого мини-языка приведено таблице 1.

Таблица 1. Семантика мини-языка словаря
Наше приложение-словарь имеет следующую семантику (перечислена от более конкретных определений к более общим):
1. Часть речиДля запроса части речи слова сообщение должно начинаться с is, за которым следует слово, а затем a или an, и далее - часть речи.
2. СинонимыДля запроса списка синонимов слова сообщение должно начинаться с synonymsOf, после чего следует слово.
3. Удаление словЧтобы удалить слово из словаря, сообщение должно начинаться с remove или delete, после чего следует слово.
4. Создание словДля создания нового слова в словаре передайте слово как метод и его определение, часть речи и необязательный список синонимов как параметры.
5. Получение определенияДля запроса определения слова передайте слово как свойство или метод.

Приложение-словарь

Приложение-словарь будет использовать базу данных, структура таблиц которой показана на рисунке 1. Если вы читаете мои статьи регулярно, вы узнаете структуру таблиц из статьи "Разметка с помощью Groovy Builders."

Рисунок 1. Простая модель базы данных для словаря
Простая модель базы данных для словаря

Примечание: Я собираюсь игнорировать столбец EXAMPLE_SENTENCE таблицы definition.

Я создам простой фасад, который будет служить интерфейсом между пользователем и базой данных. Фасад будет использовать возможности MOP Groovy, реализуя invokeMethod и getProperty. Метод invokeMethodбудет определять команды и передавать обработку соответствующему внутреннему private-методу.

Некоторые предварительные условия

Поскольку приложение будет использовать базу данных, перед тем, как продолжить, я хочу освежить вашу память в отношении GroovySql. Кроме того, я также буду использовать регулярные выражения Groovy (представленные в статье Feeling Groovy) для определения сообщений, отправленных фасаду.

Во время написания я буду выполнять тестирование фасада с помощью Groovy, поэтому, возможно, вы захотите вспомнить возможности модульного тестирования Groovy, которые я описывал в статье "Ускорение модульного тестирования кода Java с помощью Groovy ."

И, наконец, для управления состоянием базы данных в ходе тестирования я буду использовать DbUnit. Поскольку ранее в этой серии я никогда не писал о DbUnit, перед тем, как продолжить, я кратко опишу использование DbUnit с Groovy.


DbUnit, знакомьтесь - Groovy

DbUnit - это расширение JUnit, которое переводит базу данных в известное состояние между запусками тестирования. Использовать DbUnit в Groovy на удивление просто, ведь DbUnit предоставляет API, который позволяет выполнять делегирование в рамках теста. Чтобы использовать DbUnit, мне нужно указать ему соединение с базой данных и предоставить файл, содержащий данные, которые нужно передать базе данных. После этого надо просто передать все это механизму JUnit (то есть setUp())! В листинге 3 показан начальный тестовый класс приложения-словаря.

Листинг 3. Начальный тестовый класс приложения-словаря
package test.com.vanward.groovy

import com.vanward.groovy.SimpleDictionary
import groovy.util.GroovyTestCase
import java.io.File
import java.sql.Connection
import java.sql.DriverManager
import org.dbunit.database.DatabaseConnection
import org.dbunit.database.IDatabaseConnection
import org.dbunit.dataset.IDataSet
import org.dbunit.dataset.xml.FlatXmlDataSet
import org.dbunit.operation.DatabaseOperation

class DictionaryTest extends GroovyTestCase{  
   def dictionary

   void setUp() {        
      this.handleSetUpOperation()  
      dictionary = new SimpleDictionary()    
   } 

   def handleSetUpOperation() {
      def conn = this.getConnection()
      def data = this.getDataSet()       
      try{
          DatabaseOperation.CLEAN_INSERT.execute(conn, data)
      }finally{
          conn.close()
      }
    }

    def getDataSet()  {
      return new FlatXmlDataSet(new File("test/conf/words-seed.xml"))
    }

    def getConnection()  {
       Class.forName("org.gjt.mm.mysql.Driver")
       def jdbcConnection = DriverManager.
        	getConnection("jdbc:mysql://localhost/words", 
       	         "words", "words")             
       return new DatabaseConnection(jdbcConnection)
    }  
}

В листинге 3 я создал оболочку класса, которая будет служить тестовым классом по ходу разработки приложения-словаря. При вызове теста JUnit вызовет setUp(), который вызовет API DbUnit. DbUnit вставит данные из файла test/conf/words-seed.xml в базу данных. Содержимое файла test/conf/words-seed.xml можно увидеть в листинге 4.

Листинг 4. Пример файла данных
<?xml version='1.0' encoding='UTF-8'?>
<dataset>
 <word WORD_ID="1" SPELLING="pugnacious" PART_OF_SPEECH="Adjective"/>   
 <definition DEFINITION_ID="10" 
             DEFINITION="Combative in nature; belligerent." 
             WORD_ID="1" />
 <synonym SYNONYM_ID="20" WORD_ID="1" SPELLING="belligerent"/>
 <synonym SYNONYM_ID="21" WORD_ID="1" SPELLING="aggressive"/>  
 <word WORD_ID="2" SPELLING="glib" PART_OF_SPEECH="Adjective"/>
 <definition DEFINITION_ID="11" 
     	       DEFINITION="Performed with a natural, offhand ease" 
             WORD_ID="2" />             
 <definition DEFINITION_ID="12" 
  	      DEFINITION="Marked by ease and fluency of speech or 
  	      writing that often suggests or stems from insincerity, 
  	      superficiality, or deceitfulness" 
         WORD_ID="2" />              
 <synonym SYNONYM_ID="30" WORD_ID="2" SPELLING="artful"/>
 <synonym SYNONYM_ID="31" WORD_ID="2" SPELLING="suave"/>  
 <synonym SYNONYM_ID="32" WORD_ID="2" SPELLING="insincere"/>  
 <synonym SYNONYM_ID="33" WORD_ID="2" SPELLING="urbane"/>  

 <word WORD_ID="3" SPELLING="rowel" PART_OF_SPEECH="Verb"/>  
 <definition DEFINITION_ID="50" 
             DEFINITION="to vec, trouble" 
             WORD_ID="13" />
</dataset>

Элементы файла данных XML (показанные в листинге 4) соответствуют названиям таблиц. Атрибуты элементов соответствуют названиям соответствующих столбцов таблицы.


Создание мини-языка

Теперь, когда тестовый класс определен, я могу начать разработку (и тестирование) приложения. Я займусь всеми функциями в том порядке, в котором они перечислены в Таблице 1.

Группы регулярных выражений

Важнейшую роль в моем примере словаря будут играть группы регулярных выражений. В Groovy вы можете создать экземпляр Matcher из обычного языка Java, используя синтаксис =~. С помощью экземпляра Matcher можно получать фрагменты String, вызывая метод group(). Для создания групп в регулярных выражениях используются скобки. Например, регулярное выражение (synonymsOf)(.*) создает две группы. Одна группа находит точную строку String "synonymsOf" , а вторая находит любые символы, следующие за "synonymsOf". Однако стоит помнить, что перед получением значения группы вам нужно вызвать метод matches()Matcher.

1. Часть речи

Если пользователь хочет узнать часть речи слова, он передает сообщение, которое выглядит примерно следующим образом isRowelAVerb или isEstivalAnAdjective. (Обратите внимание, что я следую нормальной семантике и пытаюсь сохранить правильный английский, допуская артикли a и an.) Интеллектуальная логика ответа на вопросы сводится к определению нужного слова, которое в первом случае будет Rowel, а во втором - Estival. Также нужно определить часть речи, которая в данных примерах будет Verb (глагол) и Adjective (прилагательное) соответственно.

Логика становится простой, если использовать регулярные выражения. Шаблон имеет следующий вид is(.*)(An|A)(Verb|Adjective|Adverb|Noun). Меня интересуют первая и третья группы (слово и часть речи). Таким образом, я могу написать простой запрос к базе данных, который извлекает часть речи и сравнивает вопрос с ответом, который показан в листинге 5.

Листинг 5. Определение части речи
private determinePartOfSpeech(question){
  def matcher = question =~ 'is(.*)(An|A)(Verb|Adjective|Adverb|Noun)'
  matcher.matches()
  def word = matcher.group(1)
  def partOfSpeech = matcher.group(3)
  def row = sql.firstRow("select part_of_speech from word 
  	where spelling=?", [word])	
  return row[0] == partOfSpeech
}

Листинг 5 выглядит достаточно простым, но давайте посмотрим, что случится, если мы напишем для него несколько тестов.

Листинг 6. Тестирование определения части речи слова
void testPartOfSpeechFalse() {
  def val = dictionary.isPugnaciousAVerb()
  assertFalse("pugnacious is not a verb", val)
}	
void testPartOfSpeechTrue() {
  def val = dictionary.isPugnaciousAnAdjective()
  assertTrue("pugnacious is an Adjective", val)
}

Посмотрите еще раз на XML, приведенный в листинге 4. Поскольку я с помощью DbUnit перед запуском тестов привожу базу данных в известное состояние, я могу предположить, что у слова pugnacious верно установлена часть речи Adjective. В листинге 6 я добавил два простых теста DictionaryTest (см. листинг 3), чтобы убедиться в том, что логика работает верно.

2. Синонимы

Семантика запросов синонимов имеет следующий шаблон: synonymsOfBloviate, где за желаемым словом Bloviate следует synonymsOf. Регулярное выражение будет ещё проще: (synonymsOf)(.*). После того, как желаемое слово найдено, вызывается запрос к базе данных, который объединяет таблицы word и synonym. Возвращаемые синонимы добавляются в List и возвращаются, как показано в листинге 7.

Листинг 7. Реализация getSynonyms
private getSynonyms(question){
  def matcher = question =~ '(synonymsOf)(.*)'
  matcher.matches()
  def word = matcher.group(2).toLowerCase()
  def syns = []	
  sql.eachRow("select synonym.spelling from synonym, word " +
      "where synonym.word_id = word.word_id and " +
      "word.spelling = ?", [word]){ arow ->
          syns << arow.spelling
       }	   
  return syns		
}

Тесты, показанные в листинге 8, проверяют, что слово, для которого определены синонимы, вернёт их должным образом, а также то, что слово, для которого не определены синонимы (Rowel), вернет List.

Листинг 8. Не забудьте протестировать этот метод!
void testSynonymsForWord() {
  def val = dictionary.synonymsOfPugnacious()
  def expect = ["belligerent","aggressive"]
  assertEquals("should be: " + expect, expect, val)
}
void testNoSynonymsForWord() {
  def val = dictionary.synonymsOfRowel()
  def expect = []
  assertEquals("should be: " + expect, expect, val)
}

3. Удаление слов

Логика удаления, приведенная в листинге 9, гибкая, поскольку я позволяю использовать после слова команды remove и delete. Например, можно употребить и команду removeGlib, и deleteGlib. Таким образом, регулярное выражение будет иметь вид: (remove|delete)(.*), и наша логика дает SQL-запрос delete.

Листинг 9. Удаление слова
private removeWord(word){
  def matcher = word =~ '(remove|delete)(.*)'
  matcher.matches()
  def wordToRemove = matcher.group(2).toLowerCase()	
  sql.execute("delete from word where spelling=?" , [wordToRemove])	
}

Конечно же, такая гибкость требует написания как минимум двух тестов для удаления слова, как показано в листинге 10. Чтобы проверить, что слово действительно удалено, я пытаюсь получить его определение (см. пункт 5. Получение определения слова для получения дополнительной информации) и убедиться, что ничего не возвращается.

Листинг 10. Проверка обоих случаев
void testDeleteWord() {
  dictionary.deleteGlib()
  def val =  dictionary.glib()
  def expect = []
  assertEquals("should be: " + expect, expect, val)
}
void testRemoveWord() {
  dictionary.removePugnacious()
  def val =  dictionary.pugnacious()
  def expect = []
  assertEquals("should be: " + expect, expect, val)
}

4. Создание слов

Семантика создания нового слова - это сообщение, которое не соответствует какому-либо шаблону запроса и содержит список параметров. Например, этому шаблону соответствует сообщение echelon("Noun", ["a level within an organization"]). Слово echelon, первый параметр - это часть речи, за которой следует List, содержащий определения и необязательный третий параметр, который может быть List синонимов.

Как показано в листинге 11, создание нового слова в базе данных подразумевает добавление строки и её определения в нужные таблицы word и definition); более того, если существует List синонимов, он также добавляется.

Листинг 11. Создание слова
private createWord(word, defsAndSyms){
  def wordId = id++
  def definitionId = wordId + 10
  sql.execute("insert into word 
  	(word_id, part_of_speech, spelling) values (?, ?, ?)" , 
    [wordId, defsAndSyms[0], word])
  for(definition in defsAndSyms[1]){	
    sql.execute("insert into definition 
    	(definition_id, definition, word_id) " +
      "values (?, ?, ?)" , [definitionId, definition, wordId])
    }
  //has a list of synonyms has been passed in
  if(defsAndSyms.length > 2){
    def synonyms = defsAndSyms[2]
    synonyms.each{ syn ->
      sql.execute("insert into synonym 
      	(synonym_id, word_id, spelling) values (?, ?, ?)" , 
        [id++, wordId, syn])
      }
    }
}

Логика первичного ключа, показанная в листинге 11, подозрительно проста; однако для того, чтобы подтвердить мою уверенность, я могу написать несколько тестов

Листинг 12. Тесты для логики создания слова
void testCreateWord() {	
  dictionary.bloviate("Verb", 
  	["To discourse at length in a pompous or boastful manner"], 
    ["orate", "gabble", "lecture"])
  def val = dictionary.bloviate()
  def expect = "To discourse at length in a pompous or boastful manner"
  assertEquals("should be: " + expect, expect, val[0])
}
void testCreateWordNoSynonyms() {	
  dictionary.echelon("Noun", ["a level within an organization"])
  def val = dictionary.echelon()
  def expect = "a level within an organization"
  assertEquals("should be: " + expect, expect, val[0])
}

Как обычно, для проверки того, что всё работает как ожидалось, я написал несколько тестов, приведенных в листинге 12. После создания слова я опрашиваю экземпляр dictionary для этого же слова, чтобы убедиться, что оно есть в базе данных.

5. Получение определения слова

Все сообщения, переданные приложению-словарю, которые не соответствуют ни одному из приведенных выше типов запросов (часть речи и синоним), и который не содержит никаких параметров, считается запросом определения. Например, запрос вида .glib или .glib() вернет определение glib.

Листинг 13. Искать определение слова совсем несложно!
private getDefinitions(word){	
  def definitions = []			   	  
  sql.eachRow("select definition.definition from definition, word " +
    "where definition.word_Id = word.word_id and " +
    "word.spelling = ?", [word]){ arow ->
      definitions << arow.definition
    }	   
  return definitions	
}

Поскольку я позволяю и вызовам методов, и вызовам свойств работать как запрос определения, в листинге 14 мне нужно написать как минимум два теста. Так же, как и в листинге 6, я могу предположить, что слово pugnacious уже есть в базе данных. Не правда ли, работать с DbUnit удобно?

Листинг 14. Проверка обоих случаев - вызов метода и свойства
void testFindWord() {
  def val = dictionary.pugnacious()
  def expect = "Combative in nature; belligerent."
  assertEquals("should be: " + expect, expect, val[0])
}
void testFindWordAsProperty() {
  def val = dictionary.pugnacious
  def expect = "Combative in nature; belligerent."
  assertEquals("should be: " + expect, expect, val[0])
}

И всё это о семантике приложения-словаря. Теперь давайте подробнее рассмотрим логику реализующего её приложения.


Логика MOP

Основа приложения-словаря лежит в реализации invokeMethod и getProperty. В этих методах мне нужно принимать интеллектуальные решения об обработке переданных приложению сообщений. Решение находится в последовательности условных операторов, которые выполняют операции String для определения типа сообщения

Единственное беспокоящее условие в листинге 15 - это первое условие, которое пытается проверить, действительно ли является сообщение запросом определения, а не одной из других возможных комбинаций вида is..., remove..., delete... или synonymOf....

Листинг 15. Логическое сердце MOP
def invokeMethod(String methodName, Object params) { 	
  if(isGetDefinitions(methodName, params)){
    return getDefinitions(methodName)
  }else if (params.length > 0){	 
    createWord(methodName, params)
  }else if(methodName[0..1] == 'is'){
    return determinePartOfSpeech(methodName)
  }else if
  (methodName[0..5] == 'remove' || methodName[0..5] == 'delete'){	
  	 removeWord(methodName)
  }else if (methodName[0..9] == 'synonymsOf'){
    return getSynonyms(methodName)
  }
}

private isGetDefinitions(methodName, params){
  return !(params.length > 0) && 
    ( (methodName.length() <= 5 
    && methodName[0..1] != 'is' ) || 
    (methodName.length() <= 10 
    && isRemoveDeleteIs(methodName) ) || 
    (methodName.length() >= 10 
    && methodName[0..9] != 'synonymsOf' 
    && isRemoveDeleteIs(methodName)))	
}

private isRemoveDeleteIs(methodName){
	return (methodName[0..5] != 'remove' 
      && methodName[0..5] != 'delete' 
      && methodName[0..1] != 'is')
  }

Метод isGetDefinitions делает значительную часть работы и перекладывает часть её на isRemoveDeleteIs. После того, как он определил, что сообщение не является запросом определения, логика становится значительно проще.

Метод getProperty, показанный в листинге 16, прост, поскольку мы оговорили, что пользователи могут запрашивать определения по свойствам и никак иначе; следовательно, я вызываю метод getDefinitions при вызове getProperty.

Листинг 16. К счастью, свойства просты!
def getProperty(String property){
  return getDefinitions(property)
}

Это все! Правда. Логика, которая показана в листингах с 5 по 16, образует приложение, которое дает пользователям существенную гибкость без больших усложнений. Как уже отмечалось ранее, применяемая в нашем приложении логика первичного ключа приложения, конечно же, небезупречна. Можно предпринять множество шагов по улучшению этого аспекта, от последовательностей базы данных до таких сред, как Hibernate, которые весьма изящно обрабатывают идентификаторы.

Лучше один раз увидеть - поэтому в листинге 17 показан язык приложения-словаря во всей красе. (Ах, если бы у меня было что-нибудь подобное перед сдачей экзамена SAT!)

Листинг 17. Словарь
import com.vanward.groovy.SimpleDictionary

def dict = new SimpleDictionary()  

dict.vanward("Adjective", ["Being on, or towards the front"], 
	["Advanced"])
dict.pulchritude("Noun", ["Great physical beauty and appeal"])

dict.vanward             //prints "Being on, or towards the front"
dict.pulchritude()       //prints "Great physical beauty and appeal"

dict.synonymsOfVanward() //prints "Advanced"

dict.isVanwardANoun()    //prints "false"

dict.removeVanward()
dict.deletePulchritude()

Смысл MOC

Сейчас, вы можете спросить, а в чем смысл? Я мог бы просто объявить методы private, внести несколько поправок и сделать приложение, которое ведет себя обычным образом (то есть, я могу объявить метод getDefinition, метод createWord и т.д.) без использования MOC.

Давайте рассмотрим это предложение - скажем, я хочу создать точно такое же приложение-словарь, не используя реализацию MOC в Groovy. Мне нужно было бы переопределить список параметров для метода createWord(), чтобы он выглядел примерно следующим образом def createWord(word, partOfSpeech, defs, syns=[]){}.

Итак, как вы можете видеть, мой первый шаг состоял в замене модификатора private на def, что является аналогом объявления метода как public. После этого я определил бы параметры и сделал бы последний необязательным (syns=[]).

Также мне нужно определить метод delete, который будет как минимум вызывать метод remove для поддержки обоих вариантов удаления. Запрос на часть речи также придется изменить. Я мог бы добавить ещё один параметр, поэтому возможно, он выглядел бы следующим образом determinePartOfSpeech(word, partOfSpeech).

По существу, предприняв описанные выше действия, я перенес бы концептуальную сложность приложения-словаря с реализации MOP на статический API. Что приводит к вопросу - зачем нужно делать это? Итак, вы убедились, что реализация MOP открыла несравненную гибкость для пользователей моего приложения. Получившийся API - это не просто серия статически определенных методов, а гибкий язык!


В заключение

Если ваша голова уже пошла кругом, задумайтесь над следующим вопросом: что, если создать приложение-словарь, которое позволит пользователю создавать запросы? Например, findAllWordsLikeGlib или findAllWordsWithSynonymGlib, и так далее - эта семантика становится вопросом разборки текста на стороне MOP и золотым дном для запросов на стороне пользователя!

А теперь подумайте ещё немного: что, если создать другой мини-язык, который будет более полезен для бизнеса - например, мини-язык, ориентированный на биржевую торговлю? А если пойти дальше и адаптировать это приложение для консоли? Вместо того, чтобы писать сценарии, пользователи смогут использовать командный процессор Groovy. (Помните, что я говорил о Bash?)

Если вы думаете, что эта алхимическая утилита для LISP-эзотериков, которые возятся с расширениями Emacs, подумайте ещё раз! Просто посмотрите на другую сторону улицы, на громогласных гуру Ruby on Rails, - и вы поймете, почему эта платформа так восхитительна. Загляните внутрь и посмотрите на MOP в работе – и вы сможете создавать отличные запросы, не углубляясь в SQL, если вы будете соблюдать схему названий. Итак, кто тут занимается ерундой?


Загрузка

ОписаниеИмяРазмер
Sample codej-groovy-mop.tar.gz5 KB

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=287922
ArticleTitle=Практически Groovy : MOP и мини-языки
publish-date=02062008