Clojure多方法与协议

45

我是Clojure的新手,正在寻找一些具体的例子来说明何时使用协议和何时使用多方法。我知道协议通常用于创建类型层次结构和典型的面向对象编程,它们在多方法之后添加到语言中,并且协议通常具有更好的性能,因此我的问题是:

协议是否意味着取代多方法?如果不是,您能给我一个使用多方法而不是协议的例子吗?

4个回答

37

协议和多方法是互补的,旨在解决略有不同的使用情况。

  • 协议可以根据第一个参数的类型提供高效的多态调度。由于其能够利用一些非常高效的JVM特性,因此协议为您提供了最佳性能
  • 多方法允许非常灵活的多态性,可以基于方法参数的任何函数进行调度。多方法较慢但非常灵活

一般来说,我的建议是除非您有具体需要使用多方法,否则请使用协议。

您可能需要使用多方法的情况如下所示:

(defn balance-available? [amount balance] (> balance amount))

(defmulti withdraw balance-available?)

(defmethod withdraw true [amount balance] 
  (- balance amount))

(defmethod withdraw false [amount balance] 
  (throw (Error. "Insufficient balance available!")))

请注意,您不能在此处使用协议,原因如下:

  • 调度函数需要使用两个参数来确定要使用哪个方法实现(即它是多重分派的情况)。
  • 您也无法根据第一个参数的类型进行区分(这通常是一个数值)。

35

多态方法更加强大和昂贵,

当协议足够时请使用协议,但如果您需要根据火星上看到的月相进行分派,那么多态方法就是您最好的选择。

协议的存在使得简单的东西保持简单,并提供了一种让Clojure生成与相应Java几乎相同的字节码的方式。似乎大多数人大部分时间都使用协议。当我需要根据超过一个参数进行派遣时,我使用多态方法,尽管我必须承认这只发生过一次,而完整的 isa 层次结构很少被使用(也很少被我使用)。因此,在需要时请使用多态方法

在我的经验中,最好的例子就在开始,即core.clj 中。


11
协议会使你失去大量的动态性。当重新加载时,它们会导致非常混乱的开发行为,并将你锁定在仅通过第一个参数的类型进行调度,这使得以后更改代码非常困难。协议绝对不简单;它们是一种连接到 JVM 的低级分派的方式,以获得自我托管所需的速度。多方法确实简单——它们只是具有调度函数的变量。 - user61051
13
另一方面,如果你是一个Clojure新手,认为自己需要多态性,有很大可能是错误的。在Clojure中,真正需要定义新的多态功能的情况非常少见。 - user61051
@technomancy,您能否详细说明一下?对于一个习惯以某种方式思考和工作的Clojure新手来说,似乎很难在没有多态性的情况下完成某些任务。您能想到任何例子来说明Clojure惯用法优于多态性吗? - Lindon

6
正如Arthur所提到的,多方法更加强大但也更加昂贵。实际上,协议可以被视为多方法的一种特殊情况,其中调度函数是class。当然,这并不完全正确,因为协议不仅仅是这样。
如果您需要在其他参数的类之外进行分派,则需要使用多方法或重新设计。按类型分派是协议的一个很好的用例。

3

在不需要类层次结构的情况下,我喜欢使用多态(multimethods)。例如,如果您拥有媒体数据库,且记录如下所示:{:media-type :video, :bytes ...},那么您可以使用多态。

(defmulti make-grayscale :media-type)

然后,您可以制作各种。
; in video.clj
(defmethod make-grayscale :video [record]
  (ffmpeg ... (:bytes record))

; in photo.clj
(defmethod make-grayscale :photo [record]
  (imagemagick ... (:bytes record))

这样你就可以避免有一个中央的cond表达式,从而获得类的可重用性。但是你不必经历所有那些“包装器类层次结构”的样板代码,对我来说,这是Java世界应该留下的祸根。多方法只是函数,感觉更像Clojure。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接