clojure - 使用跨多个命名空间定义的 Clojure 多方法

标签 clojure protocols abstraction multimethod

虽然下面的例子看起来有点奇怪,但这是因为我试图将我目前遇到的一个相当大的问题简化为一个最小的例子。当它们位于几个抽象层后面并且 defmulti 和相应的 defmethods 在多个命名空间中定义时,我正在努力解决如何调用多方法。我真的觉得我在这里遗漏了一些明显的东西......

假设我有以下场景:

  • 我从各种供应商那里购买东西,通过他们自己的专有接口(interface)
  • 我想实现一个通用接口(interface)来与这些供应商中的每一个交谈
  • 我希望能够从不同的供应商处购买不同的元素

  • 使用 Clojure,实现通用接口(interface)的推荐方法是通过协议(protocol)或多方法。在这种情况下,当我根据供应商的值(value)进行切换时,我认为处理我在下面描述的情况的最佳方法是通过多种方法(但我可能是错的)。

    我的多方法定义看起来像这样,它定义了一个我想用来与每个供应商的 API 对话的通用接口(interface):
    (ns myapp.suppliers.interface)
    (defmulti purchase-item :supplier)
    (defmulti get-item-price :supplier)
    

    对于每个供应商,我可能想要类似的东西:
    (ns myapp.suppliers.supplier1
      (:require [myapp.suppliers.interface :as supplier-api]))
    (defmethod purchase-item :supplier1 [item quantity] ...)
    (defmethod get-item-price :supplier1 [item] ...)
    


    (ns myapp.suppliers.supplier2
      (:require [myapp.suppliers.interface :as supplier-api]))
    (defmethod purchase-item :supplier2 [item quantity] ...)
    (defmethod get-item-price :supplier2 [item] ...)
    

    到目前为止,没有问题

    现在到我调用这些抽象方法的代码,我假设它看起来像:
    (ns myapp.suppliers.api
      (:require [myapp.suppliers.supplier1 :as supplier1]
                [myapp.suppliers.supplier2 :as supplier2])
    (defn buy-something
      [supplier item quantity]
      (purchase-item [supplier item quantity])
    (defn price-something
      [supplier item]
      (get-item-price [supplier item])
    

    这开始看起来有点……丑陋。每次实现新供应商的 API 时,我都需要将 myapp.suppliers.api 更改为 :require 新供应商的方法并重新编译。

    现在我在下一个级别工作,我想从供应商 2 那里购买一个小部件。
    (ns myapp.core
      (:require [myapp.suppliers.api :as supplier])
    (def buy-widget-from-supplier2    
      (buy-something :supplier2 widget 1)
    

    这是行不通的,因为 :supplier2 尚未在此命名空间中的任何位置定义。

    有没有更优雅的方式来编写这段代码?特别是,在 myapp.core 中,我如何从 :supplier2 购买东西?

    最佳答案

    初始注释

    很难判断您是否在简化示例的过程中混淆了一些东西,或者它们是否不是完全正确的。对于我所指的示例,请考虑 purchase-item ,尽管问题与 get-item-price 类似:

  • defmulti 调用是单参数函数
  • defmethod 调用每个都有两个参数
  • buy-something 中的调用将向量传递给 purchase-item ,但在向量中查找 :supplier 关键字将始终返回 nil

  • 您的担忧
  • Every time I implement a new supplier's API, I'll need to change myapp.suppliers.api to :require that new supplier's methods and recompile.


  • 如果需要 myapp.suppliers.interface 命名空间 myapp.suppliers.api 可以避免问题
  • This can't work, because :supplier2 hasn't been defined anywhere in this namespace.


  • 简而言之,这将起作用。 :)
  • Is there a more elegant way to write this code? In particular, in myapp.core, how can I buy-something from :supplier2?


  • 当然,但是这个解决方案将根据 初始注释 中的歧义做出一些假设。

  • 在不偏离您的原始设计太远的情况下,这是一个完整的示例,说明了我如何解释您试图实现的目标:
  • myapp.suppliers.interface
    (ns myapp.suppliers.interface)
    
    (defmulti purchase-item (fn [supplier item quantity] supplier))
    
  • myapp.suppliers.supplier1
    (ns myapp.suppliers.supplier1
      (:require [myapp.suppliers.interface :as supplier-api]))
    
    (defmethod supplier-api/purchase-item :supplier1 [supplier item quantity]
      (format "Purchasing %dx %s from %s" quantity (str item) (str supplier)))
    
  • myapp.suppliers.supplier2
    (ns myapp.suppliers.supplier2
      (:require [myapp.suppliers.interface :as supplier-api]))
    
    (defmethod supplier-api/purchase-item :supplier2 [supplier item quantity]
      (format "Purchasing %dx %s from %s" quantity (str item) (str supplier)))
    
  • myapp.suppliers.api
    (ns myapp.suppliers.api
      (:require [myapp.suppliers.interface :as interface]))
    
    (defn buy-something [supplier item quantity]
      (interface/purchase-item supplier item quantity))
    
  • myapp.core
    (ns myapp.core
      (:require [myapp.suppliers.api :as supplier]))
    
    (def widget {:id 1234 :name "Monchkin"})
    
    (supplier/buy-something :supplier1 widget 15)
    ;;=> "Purchasing 15x {:id 1234, :name \"Monchkin\"} from :supplier1"
    
    (supplier/buy-something :supplier2 widget 3)
    ;;=> "Purchasing 3x {:id 1234, :name \"Monchkin\"} from :supplier2"
    

  • 如您所见,supplier/buy-something 调用传播到适当的多方法实现。希望这可以帮助您到达您想要去的地方。

    关于clojure - 使用跨多个命名空间定义的 Clojure 多方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39585510/

    相关文章:

    Swift 协议(protocol)一致性

    java - 如果接口(interface)只声明了方法签名,为什么我们还需要接口(interface)呢?

    java - Leiningen 尝试从错误/缺失的 JDK 调用 java.exe

    Clojure 函数体中不同位置的惰性 seq 消耗

    recursion - Clojure Koan 阶乘函数实现

    c# - 在抽象类中使用泛型

    powershell - 在powershell中是否还有其他DSL的示例?

    google-app-engine - 您使用 Clojure 做 GAE Apps 的方法是什么?

    Android使用数据传输协议(protocol)http或https

    swift - 你能解释/解决这些 'generic constraint' 编译器错误吗?