作为与使用
extend
截然不同的方法,定义一个
defdecorator
宏来代理装饰实现并提供任何缺失的协议定义并不太困难。
同样地,从像下面这样的协议开始:
(defprotocol Logger
(info [logger s])
(warn [logger s])
(debug [logger s]))
(def println-logger
(reify Logger
(info [_ s]
(println "Info:" s))
(warn [_ s]
(println "Warn:" s))
(debug [_ s]
(println "Debug:" s))))
您可以编写一些工具来创建协议定义,方法是检查协议以获取其所有函数,然后为缺失的任何函数创建委托实现:
(defn protocol-fn-matches?
"Returns the protocol function definition
if it matches the desired name and arglist."
[[name arglist :as def] desired-name desired-arglist]
(when (and (= name desired-name)
(= (count arglist) (count desired-arglist)))
def))
(defn genarglist
"Takes an arglist and generates a new one with unique symbol names."
[arglist]
(mapv (fn [arg]
(gensym (str arg)))
arglist))
(defn get-decorator-definitions
"Generates the protocol functions for a decorator,
defaulting to forwarding to the implementation if
a function is not overwritten."
[protocol-symbol impl fs]
(let [protocol-var (or (resolve protocol-symbol)
(throw (Exception. (str "Unable to resolve protocol: " protocol-symbol))))
protocol-ns (-> protocol-var meta :ns)
protocol (var-get protocol-var)]
(for [{:keys [name arglists]} (vals (:sigs protocol))
arglist arglists]
(or (some #(protocol-fn-matches? % name arglist) fs)
(let [arglist (genarglist arglist)
forwarded-args (rest arglist)
f (symbol (str protocol-ns) (str name))]
`(~name ~arglist
(~f ~impl ~@forwarded-args)))))))
您可以编写一个宏,以获取定义并创建记录,扩展给定的协议。使用get-decorator-definitions
来提供任何缺失的定义:
(defmacro defdecorator
[type-symbol fields impl & body]
(let [provided-protocols-and-defs (->> body
(partition-by symbol?)
(partition-all 2))
protocols-and-defs (mapcat (fn [[[protocol] fs]]
(cons protocol
(get-decorator-definitions protocol impl fs)))
provided-protocols-and-defs)]
`(defrecord ~type-symbol ~fields
~@protocols-and-defs)))
并使用它创建新的装饰器:
(defdecorator CapslockWarningLogger
[impl] impl
Logger
(warn [_ s]
(warn impl (clojure.string/upper-case s))))
(defdecorator SelectiveDebugLogger
[ignored impl] impl
Logger
(debug [_ s]
(when-not (ignored s)
(debug impl s))))