【问题标题】:Aspect-Oriented Programming in ClojureClojure 中的面向切面编程
【发布时间】:2011-07-31 05:07:20
【问题描述】:

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

【问题讨论】:

    标签: clojure jvm aop


    【解决方案1】:

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

    您在 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 提供的功能,对吧?
    • @jcrossley3 - 如果你愿意,你总是可以用 (def my-calculation (wrap-with-logging my-calculation)) 重新定义原始函数......
    • 虽然重新评估并不安全。如需更灵活的形式化,请参阅 Robert Hooke:github.com/technomancy/robert-hooke
    • @jcrossley3 这取决于你如何使用 AOP。例如。您可以使用围绕函数调用的around 运算符。这正是这里展示的内容
    【解决方案2】:

    恕我直言,AOP 只是某些类型的静态编程语言的产物。 AFAIKS 它通常只是一堆非标准的编译器扩展。我还没有看到任何不能用更动态的语言更好地解决的 AOP 应用程序。 Clojure 无疑是足够动态的,这甚至没有考虑宏。

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

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

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

    【讨论】:

    • 说宏是动态的有点奇怪,因为它们在编译时运行(它们是编译器的挂钩)。当你改变一个宏时,你需要重新编译所有调用它的代码。在我的书中不是很有活力......
    • 此外,AOP 与 MOP 密切相关,MOP 是 Common Lisp 的元对象协议(Pascal Constanza 也因提倡 AOP for CL 而闻名)。你认为 Common Lisp 也是静态的吗?这个答案似乎是(未)受过教育的猜测的随机集合......
    • 我认为这个答案似乎暗示(对于不熟悉 Clojure 的人)Clojure 以某种方式消除了横切关注点而无需任何额外的努力。 mikera 的答案在这方面要清楚得多,并且还包括示例,我认为这应该是公认的答案。该答案及其示例也突出了一些困难这一事实只会进一步得出结论,即 AOP/横切关注点(以任何其他名称)在 Clojure 中不会自动解决。
    【解决方案3】:

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

    作为另一个名称的 Aspect Oriented Clojure 示例,请查看 Ring web 框架

    【讨论】:

      【解决方案4】:

      好吧,您可以轻松地使用 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:也用 try/catch 包装。 PS3:不太喜欢这个。恢复为具有显式日志记录。

      【讨论】:

        猜你喜欢
        • 2023-04-03
        • 1970-01-01
        • 1970-01-01
        • 2014-05-03
        • 2010-10-26
        • 1970-01-01
        • 2010-09-23
        • 2010-09-18
        • 2010-11-24
        相关资源
        最近更新 更多