Reputation: 5134
The object was created with reify
and I need to override one of its method. The only way I found is to use classic OO decorator with another use of reify
. Is there any other way?
Upvotes: 3
Views: 1018
Reputation: 6051
I'm afraid you have to make a decorator, because Clojure has no built-in construct to delegate an object's behaviour to another object by default (which I think is called prototypal inheritance).
But that does not mean it has to be tedious - you can use a macro and reflection to automate most of the work. Here's a proof of concept:
(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))))))
)))
How you would use it:
(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"]
And the expansion:
(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))))))
This implementation generates a proxy
clause, but it could also be done with reify
.
Upvotes: 5