Clojure - реализация протокола по умолчанию для протокола смешанного использования с пользовательской реализацией

В Clojure Я хочу иметь протокол, в котором некоторые методы имеют реализацию по умолчанию, а некоторые - пользовательские. И первые относятся к последним для конфигурации. Вот пример:

(defprotocol Saving
  (save [this] "saves to mongodb")
  (collection-name [this] "must return a string representing the associated MongoDB collection"))

;Default implementation

(extend-type Object
  Saving
  ; the `save` method is common for all, so it is actually implemened here
  (save [this] (mc/insert (collection-name [this]) this))
  ; this method is custom to every other type
  (collection-name [this] "no_collection"))

;Particular implementations

(defrecord User
  [login password]
  Saving
  (collection-name [this] "users"))

(defrecord NewsItem
  [text date]
  Saving
  (collection-name [this] "news_items"))

Однако это не сработает. Даже если вызов collection-name в экземпляре User или NewsItem возвращает правильную строку коллекции, вызов save на них вызывает AbstractMethodError. Как я могу достичь этой тривиальной цели с OO-формой с помощью Clojure?

Ответ 1

Сделать функцию сохранения нормальной функцией:

(defn save [obj] (mc/insert (collection-name obj) obj))

Протокол должен иметь только collection-name

(defprotocol Saving
  (collection-name [this] "must return a string representing the associated MongoDB collection"))

И тогда каждый объект, который хочет быть "сохраненным", может реализовать этот протокол.

Помните: стиль OO часто скрывает очевидную простую вещь:)