Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/redis/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Clojure是否有一种高效、惯用的装饰方法?_Clojure_Decorator_Clojurescript - Fatal编程技术网

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

Clojure是否有一种高效、惯用的装饰方法?,clojure,decorator,clojurescript,Clojure,Decorator,Clojurescript,在Clojure(脚本)中,您使用deftype和defrecord定义编程构造。我们希望我们的每个构造都有一个特定的、定义明确的目的。我们选择将责任分离,而不是将任何一个结构演变成一个单一的、功能齐全的东西。decorator(例如,包装其他数据结构的数据结构)适用于此 例如,您有一个记录器构造。您可以使用装饰器将时间戳添加为功能。稍后,您将添加警报支持人员传呼机作为另一个装饰器。理论上,我们可以通过这种方式对任意数量的特征进行分层。我们的配置文件清楚地确定了包含哪些功能 如果我们的记录器实现

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

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

如果我们的记录器实现了一个3方法日志协议,并且每个decorator只增加了一个,那么您仍然必须在每个decorator上实现另外两个方法来维护契约api。这些“不添加任何内容”的实现只是将消息向下传递。这是尴尬的一点

构造的api越丰富,问题就越严重。考虑一个构造,它实现了一些协议和需要处理12个左右方法的东西所需的工作。


有没有一种机制、宏或技术可以克服这个问题?

一种选择是使用
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))))
(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 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))
然后使用它轻松创建装饰器:

(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
(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))))

作为一种与使用
extend
截然不同的方法,定义一个
dededecorator
宏并不难,它将通过委托给修饰的实现来提供任何缺少的协议定义

同样,从以下协议开始:

(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))))
(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) ; Generate unique names to avoid collision
                forwarded-args (rest arglist) ; Drop the "this" arg
                f (symbol (str protocol-ns) (str name))] ; Get the function in the protocol namespace
            `(~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)))
并使用它创建新的装饰器:

(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
(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))))

巧妙的。它确实按照我的要求去做,但它确实需要在一开始就采用这种方法。正如我所怀疑的,这不是该语言试图解决的一类问题(例如,
defdecotor
)。感谢您周到的回复。一种用于将数据结构与其他数据结构或函数与其他函数包装在一起的技术称为“中间件”。通常人们会使用Ring:.中间件来了解它,但根据我的经验,它是为功能而设计的(例如,只处理一种消息的合同)。当你有一个可能有几种方法(例如协议)的结构时,我提到的复杂性就产生了。问题是:如何为支持多种方法的结构构建中间件,而不必在每一层充实每个方法?中间件正是应用于函数的装饰器模式。这个问题是关于如何将它应用于多个函数的“捆绑包”。难以置信。谢谢你的努力。我将不得不进一步调查。