如何在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:我不太喜欢这个。