Как переопределить метод существующего объекта?

Объект был создан с 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,

Другие вопросы по тегам