我剛寫了我生命中最荒謬的宏來支持這個功能。可能有一種更簡單的方法 - 如果我能想到它,我一定會發布它 - 但是這給了我一種很酷,很狡猾的感覺,而且實際上似乎工作,所以...在這裏。
編輯:這是一個更簡單的方法;定義一個函數返回一個代表所有方法的常規代碼proxy
(手工編寫或自動創建 - 代碼delegating-proxy
包含一種方法),在個別實例上使用update-proxy
來替換需要替換的方法。這顯然不如瘋狂的宏觀,所以後者應該保持在低於。
這裏是新的,簡化的方法(仍然不是非常清楚,由於與位置參數的數量限制一些問題和可變參數):
;;; delegates all methods
(defmacro delegating-proxy [o class-and-ifaces ctor-args]
(let [oname (gensym)
impls (->> class-and-ifaces
(map resolve)
(mapcat #(.getDeclaredMethods ^Class %))
(group-by #(.getName ^java.lang.reflect.Method %))
(vals)
(map (fn delegating-impls [^java.lang.reflect.Method ms]
(let [mname (symbol (.getName ^java.lang.reflect.Method (first ms)))
arity-groups (partition-by #(count (.getParameterTypes ^java.lang.reflect.Method %)) ms)
max-arity (max-key #(count (.getParameterTypes ^java.lang.reflect.Method %)) ms)]
`(~mname
[email protected](remove
nil?
(map (fn [agroup]
(let [param-types (.getParameterTypes ^java.lang.reflect.Method (first agroup))
vararg? (and (seq param-types) (or (.isArray ^Class (last param-types)) (<= 20 (count param-types))))
arity ((if vararg? dec identity) (count param-types))
params (vec (repeatedly arity gensym))
params (if vararg? (conj params '& (gensym)) params)]
(when-not (and vararg? (not= arity max-arity))
(list params `(. ~oname (~mname [email protected]))))))
arity-groups)))))))]
`(let [~oname ~o]
(proxy ~class-and-ifaces ~ctor-args [email protected]))))
一個演示:
user> (def p (delegating-proxy (fn [& args] :foo) [clojure.lang.IFn] []))
#'user/p
user> (update-proxy p {"applyTo" (fn [& args] :bar)})
#<Object$IFn$4c646ebb [email protected]>
user> (p 1)
:foo
user> (apply p (seq [1]))
:bar
編輯:原始宏如下。
首先,一個演示:
user> (.invoke (delegating-proxy (fn [x y] (prn x y))
[clojure.lang.IFn] []
(invoke [x] :foo))
:bar)
:foo
user> (.invoke (delegating-proxy (fn [x y] (prn x y))
[clojure.lang.IFn] []
(invoke [x] :foo))
:bar :quux)
:bar :quux
nil
delegating-proxy
接受對象,將通過在呼籲執行不顯式實現其次是定期proxy
參數的方法將其委託。
二,代碼。我認爲可以肯定的是,潛藏在那裏的各種缺陷都是存在的。其實它的一般形狀就在那裏;沒有潛伏。如果對某人來說足夠有用,它可能會被測試&改進到一定程度的保證健壯性。
The Gist比較容易閱讀。
(defmacro delegating-proxy [o class-and-ifaces ctor-args & impls]
(let [oname (gensym)]
(letfn [(delegating-impls [^java.lang.reflect.Method ms]
(let [mname (symbol (.getName ^java.lang.reflect.Method (first ms)))
arity-groups (partition-by #(count (.getParameterTypes ^java.lang.reflect.Method %)) ms)
max-arity (max-key #(count (.getParameterTypes ^java.lang.reflect.Method %)) ms)]
`(~mname
[email protected](remove
nil?
(map (fn [agroup]
(let [param-types (.getParameterTypes ^java.lang.reflect.Method (first agroup))
vararg? (and (seq param-types) (or (.isArray ^Class (last param-types)) (<= 20 (count param-types))))
arity ((if vararg? dec identity) (count param-types))
params (vec (repeatedly arity gensym))
params (if vararg? (conj params '& (gensym)) params)]
(when-not (and vararg? (not= arity max-arity))
(list params `(. ~oname (~mname [email protected]))))))
arity-groups)))))
(combine-impls [eimpls dimpls]
(map (fn [e d]
(let [e (if (vector? (second e))
(list (first e) (next e))
e)]
(list* (first e) (concat (next e) (next d)))))
eimpls
dimpls))]
(let [klass (resolve (first class-and-ifaces))
methods (->> class-and-ifaces
(map resolve)
(mapcat #(.getDeclaredMethods ^Class %)))
eimpl-specs (set (map (juxt first (comp count second)) impls))
rm-fn (fn rm-fn [^java.lang.reflect.Method m]
(contains? eimpl-specs [(symbol (.getName m)) (count (.getParameterTypes m))]))
dimpls (->> methods
(remove rm-fn)
(remove #(let [mods (.getModifiers ^java.lang.reflect.Method %)]
(or (java.lang.reflect.Modifier/isPrivate mods)
(java.lang.reflect.Modifier/isProtected mods))))
(sort-by #(.getName ^java.lang.reflect.Method %))
(partition-by #(.getName ^java.lang.reflect.Method %))
(map delegating-impls))
dimpl-names (set (map first dimpls))
eimpl-names (set (map first eimpl-specs))
{eonly false eboth true} (group-by (comp boolean dimpl-names first) impls)
{donly false dboth true} (group-by (comp boolean eimpl-names first) dimpls)
all-impls (concat eonly donly (combine-impls eboth dboth))]
`(let [~oname ~o]
(proxy ~class-and-ifaces ~ctor-args
[email protected]))))))
非常好,謝謝!我最終需要支持多個接口,這是您的代碼相對較小的變化。複用第一個參數名稱後,只需要s /(。getMethods(resolve type))/(mapcat#(。getMethods(resolve%))types)/'和's /〜type /〜@ types /'。 – user12341234 2017-07-13 02:08:25