#java #clojure #domain-driven-design #data-modeling
#java #clojure #дизайн, управляемый доменом #моделирование данных
Вопрос:
Мне было интересно, знает ли кто-нибудь конкретный пример DSL в Clojure, который заменяет абстракцию и читаемость хорошей OO-программы (написанной, скажем, на Java).
Я пытался использовать модель данных OO (основанную на «bean», с очень абстрактными методами, которые скрывают базовые реализации) в clojure moeity.
Я знаю, что существуют «макросы» и «функции более высокого порядка», однако я никогда не видел, чтобы они применялись к набору данных реального мира, который легко понять (например, система регистрации курсов, или автосалон, или биллинговая система, или что-то в этом роде, вспомните печально известныйПримеры «JPetStore», которые Hibernate и Ibatis популяризировали в последнее десятилетие).
Существует ли какой-либо специфичный для домена шаблон для изучения того, как моделировать системы реального мира в Clojure с протоколами и функциями более высокого порядка?
Ответ №1:
Для DSL нет специальных шаблонов — вы просто берете инструменты, доступные на языке, и пытаетесь сделать его максимально удобным и близким к предметной области. Lisp просто предоставляет вам больше инструментов, чем другие языки.
Для конкретного примера хорошего DSL посмотрите на ClojureQL. Изначально SQL был создан как DSL для реляционных баз данных. И это очень удобно для работы с консоли… но не из языка программирования, такого как Java или Clojure. Java поставляется с большими фреймворками ORM, такими как Hibernate, а Clojure предлагает простой DSL, который так же удобен, как оригинальный SQL, но полностью работает как часть языка:
(select (table :users) (where (= :id 5)))
Обычным явлением в DSL на Lisp является использование таких конструкций, как defsomething
. Например, в одной книге (извините, я не помню ее названия) есть пример сопоставления с шаблоном в тексте. Автор создает модуль с несколькими сопоставителями, например ?
, для одного слова,
для одного или нескольких слов, *
для нуля или более слов и так далее. Для этой цели он создает макрос defmatcher
, который принимает некоторый синтаксис и добавляет обработчик для этого синтаксиса в центральный реестр. Это просто абстракция — вместо нескольких повторяющихся операций он вводит один макрос, сообщающий, что он на самом деле хочет сделать — определить сопоставитель. Также в этом примере используются как макросы, так и функции высокого порядка.
Итак, еще раз, в DSL на основе Lisp нет ничего особенного — вы просто описываете доменную область с помощью инструментов, которые у вас есть на вашем языке, будь то Java, Clojure или что-то еще. Просто привыкните к языковым возможностям, и вы увидите, как это должно выглядеть.
UPD.Некоторые «реальные» примеры, когда DSL на основе Lisp удобнее, чем, скажем, ООП:
Домен: car dillership
(defcar my-cool-car :wheels 4, :doors 2, :color red) ;; in Java you need Factory
(def car1 (make-car my-cool-car)) ;; and lots of methods to
;; add features to cars and
;; cars to factory
Домен: биллинговая система
(transaction ;; in Java you cannot create wrapping constructs
(withdraw account1 100) ;; so you have to use inheritance, annotations, etc.
(put account2 100)) ;; which is much more code
Домен: некоторая веб-служба, которая обрабатывает запросы нескольких типов
(defhandler :show-all (fn [params] ...)) ;; adds defined function to the
(defhandler :find-best (fn [params] ...)) ;; map of :message-type -> function
...
(defn handle [message]
(let [msg-type (:type message), msg-params (:params message)]
(if (contains? *handlers* msg-type)
((*handlers* msg-type) msg-params)
(throw (Exception. (concat "No handler for type" (:type message)))))))
В этих примерах нет ничего особенного — вы можете реализовать их все на Java или любом другом языке. Однако такие вещи, как ключевые слова (1-й пример), функции более высокого порядка (2-й пример), макросы (все 3 примера), делают ваш код более кратким и описательным.
Ответ №2:
Я не знаю, это ли то, что вы ищете, но я прочитал книгу о Clojure (Программирование Clojure; Прагматичные программисты), в которой содержится пример хорошего маленького DSL. Вы можете найти код по адресу https://github.com/stuarthalloway/lancet . По сути, lancet — это что-то вроде make или ant, но реализовано как Clojure-DSL.
Комментарии:
1. … lancet — это DSL, но на самом деле это не «реальная» проблема, поскольку большинство программистов никогда не пытались написать такую систему: ознакомьтесь с добрым старым приложением Google: jpetstore….
Ответ №3:
ООП хорош в том смысле, что мы действительно к нему привыкли, и он соответствует концепциям в реальном мире.
Вы можете увидеть, как построить систему ООП с maps и несколькими методами (что-то похожее на прототипное наследование в JS) в The Joy of Clojure, глава 9.
В книге Пола Грэма по lisp показано, как создать объектную систему на lisp. Должно быть легко адаптировать его к Clojure.
В этой книге также подробно объясняется программирование «снизу вверх», то есть построение небольших базовых блоков и их компоновка для построения конструкций более высокого уровня.
Кроме того, если вы видите, что основная концепция во многих приложениях (например, Java pet store) является процедурной / функциональной; они не используют такие понятия объектов, как обобщение или инкапсуляция.
Компонент не имеет поведения. Данные без инкапсуляции имеют геттеры / сеттеры, которые разрешают общедоступный доступ к ним. Для того, что это дает вам, вы могли бы использовать структуру C (на типизированном языке) или карту на динамическом языке, таком как Clojure.
Службы, которые работают с ними, в основном являются функциями. Они не имеют состояния и берут данные из параметров (компонентов) или базы данных. Если вам действительно нужен интерфейс, у вас есть протоколы в Clojure.
Это не так сложно.
Назовите свои компоненты, как в JAVA… Но реализуйте их как карты Clojure или как записи
Назовите свои сервисы и реализуйте их как функции.
Нужны фабрики? Создайте функции, которые принимают конфигурацию и возвращают функции.
Требуется внедрение зависимостей? Ну, вы можете использовать параметры функции.