Clojure中面向方面的编程

时间:2011-04-06 21:52:08

标签: clojure jvm aop

如何在Clojure中实现面向方面编程?在Clojure中我们需要AOP吗? 假设我们想要简单的香草Clojure解决方案(没有AspectJ)。

4 个答案:

答案 0 :(得分:21)

面向方面编程通常用于为代码添加交叉功能,否则这些代码将无可救药地与业务逻辑交织在一起。一个很好的例子是日志记录 - 你真的不希望日志记录代码分散在你的代码库中。

你真的不需要在Clojure中使用AOP,因为在Clojure中使用其他技术很容易实现这一点。

例如,您可以使用高阶函数来“包装”具有交叉功能的其他函数:

; a simple function - the "business logic"
(defn my-calculation [a b]
  (+ a b))

; higher order function that adds logging to any other function
(defn wrap-with-logging [func]
  (fn [& args]
    (let [result (apply func args)]
      (println "Log result: " result)
      result)))

; create a wrapped version of the original function with logging added
(def my-logged-calculation (wrap-with-logging my-calculation))

(my-logged-calculation 7 9)
=> Log result:  16
=> 16

答案 1 :(得分:12)

AOP IMHO只是某些静态编程语言的工件。 AFAIKS它通常只是一堆非标准的编译器扩展。我还没有看到AOP的任何应用都无法更好地解决。原生于更动态的语言。 Clojure肯定足够动态,甚至没有考虑宏。

我可能错了,但如果是这样,我需要看到一个实际的AOP用例,在纯粹的clojure中也无法实现。

编辑:只是为了清楚:我拒绝将elisp的建议视为面向方面。在动态语言中,这些只是您需要时使用的技术,除了重新定义函数定义之外不需要语言支持 - 无论如何,所有lisps都支持。

没有必要将它们视为特殊 - 您可以在clojure中轻松定义自己的类似defadvice的函数。例如,参见compojure's wrap! macro,实际上已弃用,因为您通常甚至不需要它。

答案 2 :(得分:10)

面向方面编程是在Java中实现分离的好方法。 Clojure的可组合抽象实现了这一点。另见this questionJoy Of Clojure中已经很好地介绍了该主题。

对于面向方面的Clojure的另一个名称的例子,请查看Ring web framework

答案 3 :(得分:0)

那么,使用Clojure可以更轻松地获得AOP。只需在函数中使用元数据来告知何时需要日志:

(defn ^:log my-calculation 
  [a b]
  (+ a b))

然后,您可以重新定义所有功能,并自动将其包装并带有日志记录。这段代码的一部分(下面带有解包函数):

(defn logfn
  [f topic severity error-severity]
  (fn [& args]
    (try
      (if severity
        (let [r (apply f args)]
          (log* topic {:args args, :ret r} severity)
          r)
        (apply f args))
      (catch Exception e
        (if error-severity
          (let [data {:args args, :error (treat-error e), :severity error-severity}]
            (log* topic data error-severity)
            (throw e))
          (throw e))))))

(defn logfn-ns
  "Wrap function calls for logging on call or on error.

  By default, do nothing. When any :log or :log-error, enables logging. If ^:log,
  only log on error (default severity error).

  Can customize log severity w/ e.g. ^{:log info} or on error log severity likewise."
  [ns alias]
  (doseq [s (keys (ns-interns ns))
          :let [v (ns-resolve ns s)
                f @v
                log (-> v meta :log)
                log-error (-> v meta :log-error)]
          :when (and (ifn? f)
                     (-> v meta :macro not)
                     (-> v meta :logged not)  ;; make it idempotent
                     (or log log-error))]

    (let [log (if (= log true) nil log)
          log-error (or log-error "error")
          f-with-log (logfn f
                            (str alias "/" s)
                            log
                            log-error)]
      (alter-meta! (intern ns s f-with-log)
                   (fn [x]
                     (-> x
                         (assoc :logged true)
                         (assoc :unlogged @v)))))))

(defn unlogfn-ns
  "Reverts logfn-ns."
  [ns]
  (doseq [s (keys (ns-interns ns))
          :let [v (ns-resolve ns s)]
          :when (-> v meta :logged)]
    (let [f-without-log (-> v meta :unlogged)]
      (alter-meta! (intern ns s f-without-log)
                   (fn [x]
                     (-> x
                         (dissoc :logged)
                         (dissoc :unlogged)))))))

您只需致电(log/logfn-ns 'my.namespace "some alias"),所有内容都会打包(带有日志记录(和某些记录))。

PS:我的自定义记录器上面有一个topic,它是“某些别名/函数名” PS2:还包装了w / try / catch。 PS3:不太喜欢这个。恢复为具有显式日志记录。