2016-06-18 4 views

ответ

3

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

Но это не значит, что это должно быть утомительно - вы можете использовать макрос и отражение, чтобы автоматизировать большую часть работы. Вот доказательство концепции:

(defmacro decorator 
    [clazz proto & fs] 
    (let [proto-name (gensym "proto") 
     methods (->> (clojure.reflect/reflect (resolve clazz)) 
        :members 
        (filter #(instance? clojure.reflect.Method %)) 
        (map (fn [{:keys [name parameter-types]}] 
         [name (count parameter-types)])) 
        set) 
     to-delegate (clojure.set/difference 
         methods 
         (->> fs 
         (map (fn [[name params]] 
           [name (count params)])) 
         set)) 
     method-bodies 
     (concat 
      fs ;; these are our own definitions 
      (for [[name n-params] to-delegate] 
      (let [params (->> (range n-params) 
          (map #(gensym (str "x" %))))] 
       `(~name [[email protected]] 
       (. ~proto-name (~name [email protected]))) ;; this is where we delegate to the prototype 
      )))] 
    `(let [~proto-name ~proto] 
     (proxy 
     [~clazz] [] 
     [email protected](->> method-bodies (group-by first) (sort-by first) 
      (map (fn [[name bodies]] 
        `(~name [email protected](for [[name & rest] bodies] 
           rest)))))) 
     ))) 

Как бы вы использовать его:

(decorator 
    java.util.Collection 
    [:a :b :c] 
    (size [] -1)) 
=> #object[user.proxy$java.lang.Object$Collection$4e41253d 
     0x1eae8922 
     "[email protected]"] 

И расширение:

(macroexpand-1 '(decorator 
        java.util.Collection 
        [:a :b :c] 
        (size [] -1))) 
=> 
(clojure.core/let 
[proto28109 [:a :b :c]] 
(clojure.core/proxy 
    [java.util.Collection] 
    [] 
    (add ([x028114] (. proto28109 (add x028114)))) 
    (addAll ([x028110] (. proto28109 (addAll x028110)))) 
    (clear ([] (. proto28109 (clear)))) 
    (contains ([x028118] (. proto28109 (contains x028118)))) 
    (containsAll ([x028116] (. proto28109 (containsAll x028116)))) 
    (equals ([x028119] (. proto28109 (equals x028119)))) 
    (hashCode ([] (. proto28109 (hashCode)))) 
    (isEmpty ([] (. proto28109 (isEmpty)))) 
    (iterator ([] (. proto28109 (iterator)))) 
    (parallelStream ([] (. proto28109 (parallelStream)))) 
    (remove ([x028117] (. proto28109 (remove x028117)))) 
    (removeAll ([x028115] (. proto28109 (removeAll x028115)))) 
    (removeIf ([x028111] (. proto28109 (removeIf x028111)))) 
    (retainAll ([x028112] (. proto28109 (retainAll x028112)))) 
    (size ([] -1)) 
    (spliterator ([] (. proto28109 (spliterator)))) 
    (stream ([] (. proto28109 (stream)))) 
    (toArray ([] (. proto28109 (toArray))) ([x028113] (. proto28109 (toArray x028113)))))) 

Эта реализация создает условие proxy, но это также может быть сделано с reify.

+0

Спасибо. Это всего лишь один объект с тремя методами в общей сложности, реализация 2-х протоколов (2 метода из одного протокола и 1 из другого), так что это не так уж плохо без макроса. Я просто хотел удостовериться, что я не пропустил ничего из ядра Clojure lib. Я бы выбрал макрос, если бы количество методов было больше. Объект создается во внешней библиотеке, и мне просто нужно немного настроить этот объект. – Pol