Clojure是否对装饰者有一种高效,惯用的方法?

Mar*_*rio 2 clojure decorator clojurescript

在Clojure(脚本)中,您使用deftype和定义编程构造defrecord.我们希望每个构造都有一个特定的,明确定义的目的.我们选择分离责任,而不是将任何一个构造演变成一个单一的全功能的东西.装饰器(例如,包装其他数据结构的数据结构)对此有利.

例如,您有一个记录器构造.您将时间戳添加为装饰器的功能.您稍后将警报支持人员蜂鸣器添加为另一个装饰器.理论上,我们可以通过这种方式对任意数量的特征进行分层.我们的配置文件干净地确定包含哪些功能.

如果我们的记录器实现了一个3方法的Logging协议,并且每个装饰器只增加一个,那么你仍然必须在每个装饰器上实现另外两个方法来维护契约api.这些无添加实现只是将消息传递给链.这是尴尬的一点.

构造的api越丰富,问题就越严重.考虑一个实现一些协议的构造,以及装饰处理12个左右方法的东西所需的工作.

您是否有机会,宏观或技术可以克服这个问题?

Bey*_*mor 5

一种选择是使用extend合并默认的委托功能和覆盖实现的混合.

例如,使用如下记录器协议:

(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))))
Run Code Online (Sandbox Code Playgroud)

您可以编写一个创建装饰器实现的函数,如下所示:

(defn decorate-fn
  "Creates a decorator function
   given the implementation accessor and the called function."
  [impl f]
  (fn [decorator & args]
    (apply f (impl decorator) args)))

(defn gen-decorators
  "Creates a map of decorator functions."
  [impl fs]
  (into {} (for [[k f] fs]
             [k (decorate-fn impl f)])))

(defn decorate-logger
  "Creates a logger decorator with functions
   passing through to the implementation by default."
  [impl overrides]
  (merge (gen-decorators impl
                         {:info info
                          :warn warn
                          :debug debug})
         overrides))
Run Code Online (Sandbox Code Playgroud)

然后使用它来轻松创建装饰器:

(defrecord CapslockWarningLogger [impl])

(extend CapslockWarningLogger
  Logger
  (decorate-logger :impl
                   {:warn (fn [{:keys [impl]} s]
                            (warn impl (clojure.string/upper-case s)))}))

(defrecord SelectiveDebugLogger [ignored impl])

(extend SelectiveDebugLogger
  Logger
  (decorate-logger :impl
                   {:debug (fn [{:keys [impl ignored]} s]
                             (when-not (ignored s)
                               (debug impl s)))}))

(def logger
  (->SelectiveDebugLogger #{"ignored"}
                          (->CapslockWarningLogger
                            println-logger)))

(info logger "something")
; Info: something
; => nil

(warn logger "something else")
; Warn: SOMETHING ELSE
; => nil

(debug logger "ignored")
; => nil
Run Code Online (Sandbox Code Playgroud)