如何在Clojure中实现面向方面编程?在Clojure中我们需要AOP吗? 假设我们想要简单的香草Clojure解决方案(没有AspectJ)。
答案 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 question。 Joy 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:不太喜欢这个。恢复为具有显式日志记录。