Как переопределить метод существующего объекта?
Объект был создан с reify
и мне нужно переопределить один из его методов. Единственный способ, который я нашел, это использовать классический ОО декоратор с другим использованием reify
, Есть ли другой путь?
1 ответ
Боюсь, вам нужно создать декоратор, потому что 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 [~@params]
(. ~proto-name (~name ~@params))) ;; this is where we delegate to the prototype
)))]
`(let [~proto-name ~proto]
(proxy
[~clazz] []
~@(->> method-bodies (group-by first) (sort-by first)
(map (fn [[name bodies]]
`(~name ~@(for [[name & rest] bodies]
rest))))))
)))
Как бы вы использовали это:
(decorator
java.util.Collection
[:a :b :c]
(size [] -1))
=> #object[user.proxy$java.lang.Object$Collection$4e41253d
0x1eae8922
"user.proxy$java.lang.Object$Collection$4e41253d@6abe9887"]
И расширение:
(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
,