Использование парадигмы ОО идеально для написания слабосвязанного кода, макетов и тестирования.Clojure делает это настолько простым для выполнения.
Одна проблема, с которой я сталкивался в прошлом, заключалась в зависимости кода от другого кода.Пространства имен Clojure на самом деле усугубляют проблему, если не используются должным образом.В идеале пространства имен можно макетировать, но, как я обнаружил ... есть много проблем с макетированием пространств имен:
https://groups.google.com/forum/?fromgroups=#!topic/clojure/q3PazKoRlKU
Как только вы начнете строить больше иВ более крупных приложениях пространства имен начинаются в зависимости друг от друга, и становится очень нежелательно отдельно тестировать компоненты более высокого уровня, не имея кучу зависимостей.Большинство решений включают повторное связывание функций и другую чёрную магию, но проблема в том, что наступает время тестирования, исходные зависимости все еще загружаются -> что перерастает в большую проблему, если у вас большое приложение.
Я был мотивирован на поиск альтернатив после использования библиотек базы данных.Библиотеки баз данных доставили мне столько боли - они загружаются годами и, как правило, лежат в основе вашего приложения.Очень трудно протестировать ваше приложение, не добавляя в тестовый код целую базу данных, библиотеку и связанные периферийные устройства.
Вы хотите иметь возможность упаковать свои файлы так, чтобы части вашей системы зависели от кода вашей базы данных.может быть «заменен».Методология ОО дизайна дает ответ.
Извините, ответ довольно длинный ... Я хотел дать хорошее обоснование для , почему ОО дизайн используется больше, чем как он используется,Таким образом, фактический пример должен был быть использован.Я попытался сохранить объявления ns
, чтобы структура примера приложения была как можно более понятной.
существующий код стиля clojure
В этом примере используется carmine
, которыйклиент RedisС ним относительно легко работать и он быстро запускается по сравнению с korma и datomic, но библиотека базы данных все еще остается библиотекой базы данных:
(ns redis-ex.history
(:require [taoensso.carmine :as car]
[clojure.string :as st]))
(defmacro wcr [store kdir f & args]
`(car/with-conn (:pool ~store) (:conn ~store)
(~f (st/join "/" (concat [(:ns ~store)] ~kdir)) ~@args)))
(defn empty [store kdir]
(wcr store kdir car/del))
(defn add-instance [store kdir dt data]
(wcr store kdir car/zadd dt data))
(defn get-interval [store kdir dt0 dt1]
(wcr store kdir car/zrangebyscore dt0 dt1))
(defn get-last [store kdir number]
(wcr store kdir car/zrange (- number) -1))
(defn make-store [pool conn ns]
{:pool pool
:conn conn
:ns ns})
существующий тестовый код
все функцииследует проверить ... в этом нет ничего нового, и это стандартный код clojure
(ns redis-ex.test-history0
(:require [taoensso.carmine :as car]
[redis-ex.history :as hist]))
(def store
(hist/make-store
(car/make-conn-pool)
(car/make-conn-spec)
"test"))
(hist/add-instance store ["hello"] 100 100) ;;=> 1
(hist/get-interval store ["hello"] 0 200) ;;=> [100]
объектно-ориентированный механизм диспетчеризации
Идея о том, что «ОО» не является злом, но на самом деле весьма полезна, пришла кя после просмотра этого выступления Миско Хевери:
http://www.youtube.com/watch?v=XcT4yYu_TTs
Основная идея заключается в том, что если вы хотите создать большое приложение, вы должны отделить «функциональность» (кишкипрограмма) из «проводки» (интерфейсы и зависимости).Чем меньше зависимостей, тем лучше.
Я использую хеш-карты clojure в качестве «объектов», потому что они не имеют библиотечных зависимостей и являются полностью общими (см. Брайан Марик, говорящий об использовании той же парадигмы в ruby - http://vimeo.com/34522837).
Чтобы сделатьваш код clojure «объектно-ориентированный» вам нужна следующая функция - (send
украденная из smalltalk), которая просто отправляет функцию, связанную с ключом на карте, если она связана с существующим ключом.
(defn call-if-not-nil [f & vs]
(if-not (nil? f) (apply f vs))
(defn send [obj kw & args]
(call-if-not-nil (obj kw) obj))
Я предоставляю реализацию в универсальной служебной библиотеке (https://github.com/zcaudate/hara в пространстве имен hara.fn
). Это 4 строки кода, если вы хотите реализовать ее для себя.
определение объекта «конструктор»
Теперь вы можете изменить исходную функцию make-store
для добавления функций на карту. Теперь у вас есть уровень косвенности.
;;; in the redis-ex.history namespace, make change `make-store`
;;; to add our tested function definitions as map values.
(defn make-store [pool conn ns]
{:pool pool
:conn conn
:ns ns
:empty empty
:add-instance add-instance
:get-interval get-interval
:get-last get-last})
;;; in a seperate test file, you can now test the 'OO' implementation
(ns redis-ex.test-history1
(:require [taoensso.carmine :as car]
[redis-ex.history :as hist]))
(def store
(hist/make-store
(car/make-conn-pool)
(car/make-conn-spec)
"test"))
(require '[hara.fn :as f])
(f/send store :empty ["test"])
;; => 1
(f/send store :get-instance ["test"] 100000)
;; => nil
(f/send store :add-instance ["test"]
{100000 {:timestamp 1000000 :data 23.4}
200000 {:timestamp 2000000 :data 33.4}
300000 {:timestamp 3000000 :data 43.4}
400000 {:timestamp 4000000 :data 53.4}
500000 {:timestamp 5000000 :data 63.4}})
;; => [1 1 1 1 1]
абстракция сборки
, так как функция make-store
создает объект store
, который является полностью автономным, функции могут быть определены, чтобы воспользоваться этим
(ns redis-ex.app
(:require [hara.fn :as f]))
(defn get-last-3-elements [st kdir]
(f/send st :get-last kdir 3))
и если вы хотите использовать это ... вы бы сделали что-то вроде:
(ns redis-ex.test-app0
(:use redis-ex.app
redis-ex.history)
(:require [taoensso.carmine :as car]))
(def store
(hist/make-store
(car/make-conn-pool)
(car/make-conn-spec)
"test"))
(get-last-3-elements ["test"] store)
;;=> [{:timestamp 3000000 :data 43.4} {:timestamp 4000000 :data 53.4} {:timestamp 5000000 :data 63.4}]
насмешка с clojure - стиль 'OO'
Так что реальное преимущество этого в том, чтоget-last-3-elements
метод может быть в совершенно другом пространстве имен.это вообще не зависит от реализации базы данных, поэтому для тестирования этой функции теперь требуется только легкое использование.
mocks тогда тривиально определить.Тестирование пространства имен redis-ex.usecase можно выполнять без загрузки в какие-либо библиотеки баз данных.
(ns redis-ex.test-app1
(:use redis-ex.app))
(defn make-mock-store []
{:database [{:timestamp 5000000 :data 63.4}
{:timestamp 4000000 :data 53.4}
{:timestamp 3000000 :data 43.4}
{:timestamp 2000000 :data 33.4}
{:timestamp 1000000 :data 23.4}]
:get-last (fn [store kdir number]
(->> (:database store)
(take number)
reverse))})
(def mock-store (make-mock-store))
(get-last-3-elements ["test"] mock-store)
;; => [{:timestamp 3000000 :data 43.4} {:timestamp 4000000 :data 53.4} {:timestamp 5000000 :data 63.4}]