Clojure中的面向方面编程



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

面向方面的编程通常用于向代码中添加横切功能,否则将无可救药地与业务逻辑纠缠在一起。一个很好的例子就是日志记录——你不会真的希望日志记录代码分散在代码库的任何地方。

在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

AOP在我看来只是某种静态编程语言的产物。我想它通常只是一堆非标准的编译器扩展。我还没有看到AOP的任何应用程序不能被更好地解决。在更动态的语言中原生。Clojure当然是足够动态的,这甚至不需要考虑宏。

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

编辑:只是要清楚:我拒绝看到像elisp的建议是面向方面的。在动态语言中,这些只是在需要时使用的技术,除了重新绑定函数定义之外,不需要其他语言支持——反正所有lisp都支持。

没有必要将它们视为特殊的—您可以轻松地在clojure中定义自己的类似于默认建议的函数。例如,compojure的wrap!

面向方面编程是在Java中实现关注点分离的好方法。Clojure的可组合抽象很好地实现了这一点。看看这个问题。这个话题在《Clojure的乐趣》一书中有很好的阐述。

关于面向方面的Clojure的另一个例子,请查看Ring web框架

你可以更容易地使用Clojure。只需在函数中使用元数据来通知何时需要日志:

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

然后你可以重新定义所有的函数,用/logging自动包装它们。部分代码(连同下面的unwrap函数):

(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"),所有的都是用/logging(和一些)包装的。

PS:我的自定义记录器上面有一个topic是"一些别名/函数名"PS2:也用/try/catch来包装。PS3:我不太喜欢这个。

相关内容

  • 没有找到相关文章

最新更新